1 | /* repos-test.c --- tests for the filesystem |
---|
2 | * |
---|
3 | * ==================================================================== |
---|
4 | * Copyright (c) 2000-2008 CollabNet. All rights reserved. |
---|
5 | * |
---|
6 | * This software is licensed as described in the file COPYING, which |
---|
7 | * you should have received as part of this distribution. The terms |
---|
8 | * are also available at http://subversion.tigris.org/license-1.html. |
---|
9 | * If newer versions of this license are posted there, you may use a |
---|
10 | * newer version instead, at your option. |
---|
11 | * |
---|
12 | * This software consists of voluntary contributions made by many |
---|
13 | * individuals. For exact contribution history, see the revision |
---|
14 | * history and logs, available at http://subversion.tigris.org/. |
---|
15 | * ==================================================================== |
---|
16 | */ |
---|
17 | |
---|
18 | #include <stdlib.h> |
---|
19 | #include <string.h> |
---|
20 | #include <apr_pools.h> |
---|
21 | |
---|
22 | #include "../svn_test.h" |
---|
23 | |
---|
24 | #include "svn_pools.h" |
---|
25 | #include "svn_error.h" |
---|
26 | #include "svn_fs.h" |
---|
27 | #include "svn_repos.h" |
---|
28 | #include "svn_path.h" |
---|
29 | #include "svn_delta.h" |
---|
30 | #include "svn_config.h" |
---|
31 | #include "svn_props.h" |
---|
32 | |
---|
33 | #include "../svn_test_fs.h" |
---|
34 | |
---|
35 | #include "dir-delta-editor.h" |
---|
36 | |
---|
37 | /* Used to terminate lines in large multi-line string literals. */ |
---|
38 | #define NL APR_EOL_STR |
---|
39 | |
---|
40 | #ifndef MAX |
---|
41 | #define MAX(a,b) (((a)>(b))?(a):(b)) |
---|
42 | #endif |
---|
43 | #ifndef MIN |
---|
44 | #define MIN(a,b) (((a)<(b))?(a):(b)) |
---|
45 | #endif |
---|
46 | |
---|
47 | |
---|
48 | static svn_error_t * |
---|
49 | dir_deltas(const char **msg, |
---|
50 | svn_boolean_t msg_only, |
---|
51 | svn_test_opts_t *opts, |
---|
52 | apr_pool_t *pool) |
---|
53 | { |
---|
54 | svn_repos_t *repos; |
---|
55 | svn_fs_t *fs; |
---|
56 | svn_fs_txn_t *txn; |
---|
57 | svn_fs_root_t *txn_root, *revision_root; |
---|
58 | svn_revnum_t youngest_rev; |
---|
59 | void *edit_baton; |
---|
60 | const svn_delta_editor_t *editor; |
---|
61 | svn_test__tree_t expected_trees[8]; |
---|
62 | int revision_count = 0; |
---|
63 | int i, j; |
---|
64 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
65 | |
---|
66 | *msg = "test svn_repos_dir_delta2"; |
---|
67 | |
---|
68 | if (msg_only) |
---|
69 | return SVN_NO_ERROR; |
---|
70 | |
---|
71 | /* The Test Plan |
---|
72 | |
---|
73 | The filesystem function svn_repos_dir_delta2 exists to drive an |
---|
74 | editor in such a way that given a source tree S and a target tree |
---|
75 | T, that editor manipulation will transform S into T, insomuch as |
---|
76 | directories and files, and their contents and properties, go. |
---|
77 | The general notion of the test plan will be to create pairs of |
---|
78 | trees (S, T), and an editor that edits a copy of tree S, run them |
---|
79 | through svn_repos_dir_delta2, and then verify that the edited copy of |
---|
80 | S is identical to T when it is all said and done. */ |
---|
81 | |
---|
82 | /* Create a filesystem and repository. */ |
---|
83 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-dir-deltas", |
---|
84 | opts, pool)); |
---|
85 | fs = svn_repos_fs(repos); |
---|
86 | expected_trees[revision_count].num_entries = 0; |
---|
87 | expected_trees[revision_count++].entries = 0; |
---|
88 | |
---|
89 | /* Prepare a txn to receive the greek tree. */ |
---|
90 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
91 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
92 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
93 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
94 | |
---|
95 | /***********************************************************************/ |
---|
96 | /* REVISION 1 */ |
---|
97 | /***********************************************************************/ |
---|
98 | { |
---|
99 | static svn_test__tree_entry_t expected_entries[] = { |
---|
100 | /* path, contents (0 = dir) */ |
---|
101 | { "iota", "This is the file 'iota'.\n" }, |
---|
102 | { "A", 0 }, |
---|
103 | { "A/mu", "This is the file 'mu'.\n" }, |
---|
104 | { "A/B", 0 }, |
---|
105 | { "A/B/lambda", "This is the file 'lambda'.\n" }, |
---|
106 | { "A/B/E", 0 }, |
---|
107 | { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
---|
108 | { "A/B/E/beta", "This is the file 'beta'.\n" }, |
---|
109 | { "A/B/F", 0 }, |
---|
110 | { "A/C", 0 }, |
---|
111 | { "A/D", 0 }, |
---|
112 | { "A/D/gamma", "This is the file 'gamma'.\n" }, |
---|
113 | { "A/D/G", 0 }, |
---|
114 | { "A/D/G/pi", "This is the file 'pi'.\n" }, |
---|
115 | { "A/D/G/rho", "This is the file 'rho'.\n" }, |
---|
116 | { "A/D/G/tau", "This is the file 'tau'.\n" }, |
---|
117 | { "A/D/H", 0 }, |
---|
118 | { "A/D/H/chi", "This is the file 'chi'.\n" }, |
---|
119 | { "A/D/H/psi", "This is the file 'psi'.\n" }, |
---|
120 | { "A/D/H/omega", "This is the file 'omega'.\n" } |
---|
121 | }; |
---|
122 | expected_trees[revision_count].entries = expected_entries; |
---|
123 | expected_trees[revision_count].num_entries = 20; |
---|
124 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, |
---|
125 | youngest_rev, subpool)); |
---|
126 | SVN_ERR(svn_test__validate_tree |
---|
127 | (revision_root, expected_trees[revision_count].entries, |
---|
128 | expected_trees[revision_count].num_entries, subpool)); |
---|
129 | revision_count++; |
---|
130 | } |
---|
131 | svn_pool_clear(subpool); |
---|
132 | |
---|
133 | /* Make a new txn based on the youngest revision, make some changes, |
---|
134 | and commit those changes (which makes a new youngest |
---|
135 | revision). */ |
---|
136 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
137 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
138 | { |
---|
139 | static svn_test__txn_script_command_t script_entries[] = { |
---|
140 | { 'a', "A/delta", "This is the file 'delta'.\n" }, |
---|
141 | { 'a', "A/epsilon", "This is the file 'epsilon'.\n" }, |
---|
142 | { 'a', "A/B/Z", 0 }, |
---|
143 | { 'a', "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
---|
144 | { 'd', "A/C", 0 }, |
---|
145 | { 'd', "A/mu", "" }, |
---|
146 | { 'd', "A/D/G/tau", "" }, |
---|
147 | { 'd', "A/D/H/omega", "" }, |
---|
148 | { 'e', "iota", "Changed file 'iota'.\n" }, |
---|
149 | { 'e', "A/D/G/rho", "Changed file 'rho'.\n" } |
---|
150 | }; |
---|
151 | SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 10, |
---|
152 | subpool)); |
---|
153 | } |
---|
154 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
155 | |
---|
156 | /***********************************************************************/ |
---|
157 | /* REVISION 2 */ |
---|
158 | /***********************************************************************/ |
---|
159 | { |
---|
160 | static svn_test__tree_entry_t expected_entries[] = { |
---|
161 | /* path, contents (0 = dir) */ |
---|
162 | { "iota", "Changed file 'iota'.\n" }, |
---|
163 | { "A", 0 }, |
---|
164 | { "A/delta", "This is the file 'delta'.\n" }, |
---|
165 | { "A/epsilon", "This is the file 'epsilon'.\n" }, |
---|
166 | { "A/B", 0 }, |
---|
167 | { "A/B/lambda", "This is the file 'lambda'.\n" }, |
---|
168 | { "A/B/E", 0 }, |
---|
169 | { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
---|
170 | { "A/B/E/beta", "This is the file 'beta'.\n" }, |
---|
171 | { "A/B/F", 0 }, |
---|
172 | { "A/B/Z", 0 }, |
---|
173 | { "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
---|
174 | { "A/D", 0 }, |
---|
175 | { "A/D/gamma", "This is the file 'gamma'.\n" }, |
---|
176 | { "A/D/G", 0 }, |
---|
177 | { "A/D/G/pi", "This is the file 'pi'.\n" }, |
---|
178 | { "A/D/G/rho", "Changed file 'rho'.\n" }, |
---|
179 | { "A/D/H", 0 }, |
---|
180 | { "A/D/H/chi", "This is the file 'chi'.\n" }, |
---|
181 | { "A/D/H/psi", "This is the file 'psi'.\n" } |
---|
182 | }; |
---|
183 | expected_trees[revision_count].entries = expected_entries; |
---|
184 | expected_trees[revision_count].num_entries = 20; |
---|
185 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, |
---|
186 | youngest_rev, subpool)); |
---|
187 | SVN_ERR(svn_test__validate_tree |
---|
188 | (revision_root, expected_trees[revision_count].entries, |
---|
189 | expected_trees[revision_count].num_entries, subpool)); |
---|
190 | revision_count++; |
---|
191 | } |
---|
192 | svn_pool_clear(subpool); |
---|
193 | |
---|
194 | /* Make a new txn based on the youngest revision, make some changes, |
---|
195 | and commit those changes (which makes a new youngest |
---|
196 | revision). */ |
---|
197 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
198 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
199 | { |
---|
200 | static svn_test__txn_script_command_t script_entries[] = { |
---|
201 | { 'a', "A/mu", "Re-added file 'mu'.\n" }, |
---|
202 | { 'a', "A/D/H/omega", 0 }, /* re-add omega as directory! */ |
---|
203 | { 'd', "iota", "" }, |
---|
204 | { 'e', "A/delta", "This is the file 'delta'.\nLine 2.\n" } |
---|
205 | }; |
---|
206 | SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 4, subpool)); |
---|
207 | } |
---|
208 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
209 | |
---|
210 | /***********************************************************************/ |
---|
211 | /* REVISION 3 */ |
---|
212 | /***********************************************************************/ |
---|
213 | { |
---|
214 | static svn_test__tree_entry_t expected_entries[] = { |
---|
215 | /* path, contents (0 = dir) */ |
---|
216 | { "A", 0 }, |
---|
217 | { "A/delta", "This is the file 'delta'.\nLine 2.\n" }, |
---|
218 | { "A/epsilon", "This is the file 'epsilon'.\n" }, |
---|
219 | { "A/mu", "Re-added file 'mu'.\n" }, |
---|
220 | { "A/B", 0 }, |
---|
221 | { "A/B/lambda", "This is the file 'lambda'.\n" }, |
---|
222 | { "A/B/E", 0 }, |
---|
223 | { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
---|
224 | { "A/B/E/beta", "This is the file 'beta'.\n" }, |
---|
225 | { "A/B/F", 0 }, |
---|
226 | { "A/B/Z", 0 }, |
---|
227 | { "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
---|
228 | { "A/D", 0 }, |
---|
229 | { "A/D/gamma", "This is the file 'gamma'.\n" }, |
---|
230 | { "A/D/G", 0 }, |
---|
231 | { "A/D/G/pi", "This is the file 'pi'.\n" }, |
---|
232 | { "A/D/G/rho", "Changed file 'rho'.\n" }, |
---|
233 | { "A/D/H", 0 }, |
---|
234 | { "A/D/H/chi", "This is the file 'chi'.\n" }, |
---|
235 | { "A/D/H/psi", "This is the file 'psi'.\n" }, |
---|
236 | { "A/D/H/omega", 0 } |
---|
237 | }; |
---|
238 | expected_trees[revision_count].entries = expected_entries; |
---|
239 | expected_trees[revision_count].num_entries = 21; |
---|
240 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, |
---|
241 | youngest_rev, subpool)); |
---|
242 | SVN_ERR(svn_test__validate_tree |
---|
243 | (revision_root, expected_trees[revision_count].entries, |
---|
244 | expected_trees[revision_count].num_entries, subpool)); |
---|
245 | revision_count++; |
---|
246 | } |
---|
247 | svn_pool_clear(subpool); |
---|
248 | |
---|
249 | /* Make a new txn based on the youngest revision, make some changes, |
---|
250 | and commit those changes (which makes a new youngest |
---|
251 | revision). */ |
---|
252 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
253 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
254 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, youngest_rev, subpool)); |
---|
255 | SVN_ERR(svn_fs_copy(revision_root, "A/D/G", |
---|
256 | txn_root, "A/D/G2", |
---|
257 | subpool)); |
---|
258 | SVN_ERR(svn_fs_copy(revision_root, "A/epsilon", |
---|
259 | txn_root, "A/B/epsilon", |
---|
260 | subpool)); |
---|
261 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
262 | |
---|
263 | /***********************************************************************/ |
---|
264 | /* REVISION 4 */ |
---|
265 | /***********************************************************************/ |
---|
266 | { |
---|
267 | static svn_test__tree_entry_t expected_entries[] = { |
---|
268 | /* path, contents (0 = dir) */ |
---|
269 | { "A", 0 }, |
---|
270 | { "A/delta", "This is the file 'delta'.\nLine 2.\n" }, |
---|
271 | { "A/epsilon", "This is the file 'epsilon'.\n" }, |
---|
272 | { "A/mu", "Re-added file 'mu'.\n" }, |
---|
273 | { "A/B", 0 }, |
---|
274 | { "A/B/epsilon", "This is the file 'epsilon'.\n" }, |
---|
275 | { "A/B/lambda", "This is the file 'lambda'.\n" }, |
---|
276 | { "A/B/E", 0 }, |
---|
277 | { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
---|
278 | { "A/B/E/beta", "This is the file 'beta'.\n" }, |
---|
279 | { "A/B/F", 0 }, |
---|
280 | { "A/B/Z", 0 }, |
---|
281 | { "A/B/Z/zeta", "This is the file 'zeta'.\n" }, |
---|
282 | { "A/D", 0 }, |
---|
283 | { "A/D/gamma", "This is the file 'gamma'.\n" }, |
---|
284 | { "A/D/G", 0 }, |
---|
285 | { "A/D/G/pi", "This is the file 'pi'.\n" }, |
---|
286 | { "A/D/G/rho", "Changed file 'rho'.\n" }, |
---|
287 | { "A/D/G2", 0 }, |
---|
288 | { "A/D/G2/pi", "This is the file 'pi'.\n" }, |
---|
289 | { "A/D/G2/rho", "Changed file 'rho'.\n" }, |
---|
290 | { "A/D/H", 0 }, |
---|
291 | { "A/D/H/chi", "This is the file 'chi'.\n" }, |
---|
292 | { "A/D/H/psi", "This is the file 'psi'.\n" }, |
---|
293 | { "A/D/H/omega", 0 } |
---|
294 | }; |
---|
295 | expected_trees[revision_count].entries = expected_entries; |
---|
296 | expected_trees[revision_count].num_entries = 25; |
---|
297 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, |
---|
298 | youngest_rev, pool)); |
---|
299 | SVN_ERR(svn_test__validate_tree |
---|
300 | (revision_root, expected_trees[revision_count].entries, |
---|
301 | expected_trees[revision_count].num_entries, subpool)); |
---|
302 | revision_count++; |
---|
303 | } |
---|
304 | svn_pool_clear(subpool); |
---|
305 | |
---|
306 | /* THE BIG IDEA: Now that we have a collection of revisions, let's |
---|
307 | first make sure that given any two revisions, we can get the |
---|
308 | right delta between them. We'll do this by selecting our two |
---|
309 | revisions, R1 and R2, basing a transaction off R1, deltafying the |
---|
310 | txn with respect to R2, and then making sure our final txn looks |
---|
311 | exactly like R2. This should work regardless of the |
---|
312 | chronological order in which R1 and R2 were created. */ |
---|
313 | for (i = 0; i < revision_count; i++) |
---|
314 | { |
---|
315 | for (j = 0; j < revision_count; j++) |
---|
316 | { |
---|
317 | /* Prepare a txn that will receive the changes from |
---|
318 | svn_repos_dir_delta2 */ |
---|
319 | SVN_ERR(svn_fs_begin_txn(&txn, fs, i, subpool)); |
---|
320 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
321 | |
---|
322 | /* Get the editor that will be modifying our transaction. */ |
---|
323 | SVN_ERR(dir_delta_get_editor(&editor, |
---|
324 | &edit_baton, |
---|
325 | fs, |
---|
326 | txn_root, |
---|
327 | "", |
---|
328 | subpool)); |
---|
329 | |
---|
330 | /* Here's the kicker...do the directory delta. */ |
---|
331 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, j, subpool)); |
---|
332 | SVN_ERR(svn_repos_dir_delta2(txn_root, |
---|
333 | "", |
---|
334 | "", |
---|
335 | revision_root, |
---|
336 | "", |
---|
337 | editor, |
---|
338 | edit_baton, |
---|
339 | NULL, |
---|
340 | NULL, |
---|
341 | TRUE, |
---|
342 | svn_depth_infinity, |
---|
343 | FALSE, |
---|
344 | FALSE, |
---|
345 | subpool)); |
---|
346 | |
---|
347 | /* Hopefully at this point our transaction has been modified |
---|
348 | to look exactly like our latest revision. We'll check |
---|
349 | that. */ |
---|
350 | SVN_ERR(svn_test__validate_tree |
---|
351 | (txn_root, expected_trees[j].entries, |
---|
352 | expected_trees[j].num_entries, subpool)); |
---|
353 | |
---|
354 | /* We don't really want to do anything with this |
---|
355 | transaction...so we'll abort it (good for software, bad |
---|
356 | bad bad for society). */ |
---|
357 | svn_error_clear(svn_fs_abort_txn(txn, subpool)); |
---|
358 | svn_pool_clear(subpool); |
---|
359 | } |
---|
360 | } |
---|
361 | |
---|
362 | svn_pool_destroy(subpool); |
---|
363 | |
---|
364 | return SVN_NO_ERROR; |
---|
365 | } |
---|
366 | |
---|
367 | |
---|
368 | static svn_error_t * |
---|
369 | node_tree_delete_under_copy(const char **msg, |
---|
370 | svn_boolean_t msg_only, |
---|
371 | svn_test_opts_t *opts, |
---|
372 | apr_pool_t *pool) |
---|
373 | { |
---|
374 | svn_repos_t *repos; |
---|
375 | svn_fs_t *fs; |
---|
376 | svn_fs_txn_t *txn; |
---|
377 | svn_fs_root_t *txn_root, *revision_root, *revision_2_root; |
---|
378 | svn_revnum_t youngest_rev; |
---|
379 | void *edit_baton; |
---|
380 | const svn_delta_editor_t *editor; |
---|
381 | svn_repos_node_t *tree; |
---|
382 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
383 | |
---|
384 | *msg = "test deletions under copies in node_tree code"; |
---|
385 | |
---|
386 | if (msg_only) |
---|
387 | return SVN_NO_ERROR; |
---|
388 | |
---|
389 | /* Create a filesystem and repository. */ |
---|
390 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-del-under-copy", |
---|
391 | opts, pool)); |
---|
392 | fs = svn_repos_fs(repos); |
---|
393 | |
---|
394 | /* Prepare a txn to receive the greek tree. */ |
---|
395 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); |
---|
396 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); |
---|
397 | |
---|
398 | /* Create and commit the greek tree. */ |
---|
399 | SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); |
---|
400 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); |
---|
401 | |
---|
402 | /* Now, commit again, this time after copying a directory, and then |
---|
403 | deleting some paths under that directory. */ |
---|
404 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, youngest_rev, pool)); |
---|
405 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); |
---|
406 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); |
---|
407 | SVN_ERR(svn_fs_copy(revision_root, "A", txn_root, "Z", pool)); |
---|
408 | SVN_ERR(svn_fs_delete(txn_root, "Z/D/G/rho", pool)); |
---|
409 | SVN_ERR(svn_fs_delete(txn_root, "Z/D/H", pool)); |
---|
410 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); |
---|
411 | |
---|
412 | /* Now, we run the node_tree editor code, and see that a) it doesn't |
---|
413 | bomb out, and b) that our nodes are all good. */ |
---|
414 | SVN_ERR(svn_fs_revision_root(&revision_2_root, fs, youngest_rev, pool)); |
---|
415 | SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos, |
---|
416 | revision_root, revision_2_root, |
---|
417 | pool, subpool)); |
---|
418 | SVN_ERR(svn_repos_replay2(revision_2_root, "", SVN_INVALID_REVNUM, FALSE, |
---|
419 | editor, edit_baton, NULL, NULL, subpool)); |
---|
420 | |
---|
421 | /* Get the root of the generated tree, and cleanup our mess. */ |
---|
422 | tree = svn_repos_node_from_baton(edit_baton); |
---|
423 | svn_pool_destroy(subpool); |
---|
424 | |
---|
425 | /* See that we got what we expected (fortunately, svn_repos_replay |
---|
426 | drivers editor paths in a predictable fashion!). */ |
---|
427 | |
---|
428 | if (! (tree /* / */ |
---|
429 | && tree->child /* /Z */ |
---|
430 | && tree->child->child /* /Z/D */ |
---|
431 | && tree->child->child->child /* /Z/D/G */ |
---|
432 | && tree->child->child->child->child /* /Z/D/G/rho */ |
---|
433 | && tree->child->child->child->sibling)) /* /Z/D/H */ |
---|
434 | return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
---|
435 | "Generated node tree is bogus."); |
---|
436 | |
---|
437 | if (! ((strcmp(tree->name, "") == 0) |
---|
438 | && (strcmp(tree->child->name, "Z") == 0) |
---|
439 | && (strcmp(tree->child->child->name, "D") == 0) |
---|
440 | && (strcmp(tree->child->child->child->name, "G") == 0) |
---|
441 | && ((strcmp(tree->child->child->child->child->name, "rho") == 0) |
---|
442 | && (tree->child->child->child->child->kind == svn_node_file) |
---|
443 | && (tree->child->child->child->child->action == 'D')) |
---|
444 | && ((strcmp(tree->child->child->child->sibling->name, "H") == 0) |
---|
445 | && (tree->child->child->child->sibling->kind == svn_node_dir) |
---|
446 | && (tree->child->child->child->sibling->action == 'D')))) |
---|
447 | return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
---|
448 | "Generated node tree is bogus."); |
---|
449 | |
---|
450 | return SVN_NO_ERROR; |
---|
451 | } |
---|
452 | |
---|
453 | |
---|
454 | /* Helper for revisions_changed(). */ |
---|
455 | static const char * |
---|
456 | print_chrevs(const apr_array_header_t *revs_got, |
---|
457 | int num_revs_expected, |
---|
458 | const svn_revnum_t *revs_expected, |
---|
459 | apr_pool_t *pool) |
---|
460 | { |
---|
461 | int i; |
---|
462 | const char *outstr; |
---|
463 | svn_revnum_t rev; |
---|
464 | |
---|
465 | outstr = apr_psprintf(pool, "Got: { "); |
---|
466 | if (revs_got) |
---|
467 | { |
---|
468 | for (i = 0; i < revs_got->nelts; i++) |
---|
469 | { |
---|
470 | rev = APR_ARRAY_IDX(revs_got, i, svn_revnum_t); |
---|
471 | outstr = apr_pstrcat(pool, |
---|
472 | outstr, |
---|
473 | apr_psprintf(pool, "%ld ", rev), |
---|
474 | NULL); |
---|
475 | } |
---|
476 | } |
---|
477 | outstr = apr_pstrcat(pool, outstr, "} Expected: { ", NULL); |
---|
478 | for (i = 0; i < num_revs_expected; i++) |
---|
479 | { |
---|
480 | outstr = apr_pstrcat(pool, |
---|
481 | outstr, |
---|
482 | apr_psprintf(pool, "%ld ", |
---|
483 | revs_expected[i]), |
---|
484 | NULL); |
---|
485 | } |
---|
486 | return apr_pstrcat(pool, outstr, "}", NULL); |
---|
487 | } |
---|
488 | |
---|
489 | |
---|
490 | /* Implements svn_repos_history_func_t interface. Accumulate history |
---|
491 | revisions the apr_array_header_t * which is the BATON. */ |
---|
492 | static svn_error_t * |
---|
493 | history_to_revs_array(void *baton, |
---|
494 | const char *path, |
---|
495 | svn_revnum_t revision, |
---|
496 | apr_pool_t *pool) |
---|
497 | { |
---|
498 | apr_array_header_t *revs_array = baton; |
---|
499 | APR_ARRAY_PUSH(revs_array, svn_revnum_t) = revision; |
---|
500 | return SVN_NO_ERROR; |
---|
501 | } |
---|
502 | |
---|
503 | struct revisions_changed_results |
---|
504 | { |
---|
505 | const char *path; |
---|
506 | int num_revs; |
---|
507 | svn_revnum_t revs_changed[11]; |
---|
508 | }; |
---|
509 | |
---|
510 | |
---|
511 | static svn_error_t * |
---|
512 | revisions_changed(const char **msg, |
---|
513 | svn_boolean_t msg_only, |
---|
514 | svn_test_opts_t *opts, |
---|
515 | apr_pool_t *pool) |
---|
516 | { |
---|
517 | apr_pool_t *spool = svn_pool_create(pool); |
---|
518 | svn_repos_t *repos; |
---|
519 | svn_fs_t *fs; |
---|
520 | svn_fs_txn_t *txn; |
---|
521 | svn_fs_root_t *txn_root, *rev_root; |
---|
522 | svn_revnum_t youngest_rev = 0; |
---|
523 | |
---|
524 | *msg = "test svn_repos_history() (partially)"; |
---|
525 | |
---|
526 | if (msg_only) |
---|
527 | return SVN_NO_ERROR; |
---|
528 | |
---|
529 | /* Create a filesystem and repository. */ |
---|
530 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-revisions-changed", |
---|
531 | opts, pool)); |
---|
532 | fs = svn_repos_fs(repos); |
---|
533 | |
---|
534 | /*** Testing Algorithm *** |
---|
535 | |
---|
536 | 1. Create a greek tree in revision 1. |
---|
537 | 2. Make a series of new revisions, changing a file here and file |
---|
538 | there. |
---|
539 | 3. Loop over each path in each revision, verifying that we get |
---|
540 | the right revisions-changed array back from the filesystem. |
---|
541 | */ |
---|
542 | |
---|
543 | /* Created the greek tree in revision 1. */ |
---|
544 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
545 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
546 | SVN_ERR(svn_test__create_greek_tree(txn_root, spool)); |
---|
547 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
548 | svn_pool_clear(spool); |
---|
549 | |
---|
550 | /* Revision 2 - mu, alpha, omega */ |
---|
551 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
552 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
553 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/mu", "2", spool)); |
---|
554 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", "2", spool)); |
---|
555 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/omega", "2", spool)); |
---|
556 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
557 | svn_pool_clear(spool); |
---|
558 | |
---|
559 | /* Revision 3 - iota, lambda, psi, omega */ |
---|
560 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
561 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
562 | SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "3", spool)); |
---|
563 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/lambda", "3", spool)); |
---|
564 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/psi", "3", spool)); |
---|
565 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/omega", "3", spool)); |
---|
566 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
567 | svn_pool_clear(spool); |
---|
568 | |
---|
569 | /* Revision 4 - iota, beta, gamma, pi, rho */ |
---|
570 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
571 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
572 | SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "4", spool)); |
---|
573 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/beta", "4", spool)); |
---|
574 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/gamma", "4", spool)); |
---|
575 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/pi", "4", spool)); |
---|
576 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/rho", "4", spool)); |
---|
577 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
578 | svn_pool_clear(spool); |
---|
579 | |
---|
580 | /* Revision 5 - mu, alpha, tau, chi */ |
---|
581 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
582 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
583 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/mu", "5", spool)); |
---|
584 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", "5", spool)); |
---|
585 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/tau", "5", spool)); |
---|
586 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "5", spool)); |
---|
587 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
588 | svn_pool_clear(spool); |
---|
589 | |
---|
590 | /* Revision 6 - move A/D to A/Z */ |
---|
591 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
592 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
593 | SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); |
---|
594 | SVN_ERR(svn_fs_copy(rev_root, "A/D", txn_root, "A/Z", spool)); |
---|
595 | SVN_ERR(svn_fs_delete(txn_root, "A/D", spool)); |
---|
596 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
597 | svn_pool_clear(spool); |
---|
598 | |
---|
599 | /* Revision 7 - edit A/Z/G/pi */ |
---|
600 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
601 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
602 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/Z/G/pi", "7", spool)); |
---|
603 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
604 | svn_pool_clear(spool); |
---|
605 | |
---|
606 | /* Revision 8 - move A/Z back to A/D, edit iota */ |
---|
607 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
608 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
609 | SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); |
---|
610 | SVN_ERR(svn_fs_copy(rev_root, "A/Z", txn_root, "A/D", spool)); |
---|
611 | SVN_ERR(svn_fs_delete(txn_root, "A/Z", spool)); |
---|
612 | SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "8", spool)); |
---|
613 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
614 | svn_pool_clear(spool); |
---|
615 | |
---|
616 | /* Revision 9 - copy A/D/G to A/D/Q */ |
---|
617 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
618 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
619 | SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); |
---|
620 | SVN_ERR(svn_fs_copy(rev_root, "A/D/G", txn_root, "A/D/Q", spool)); |
---|
621 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
622 | svn_pool_clear(spool); |
---|
623 | |
---|
624 | /* Revision 10 - edit A/D/Q/pi and A/D/Q/rho */ |
---|
625 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool)); |
---|
626 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool)); |
---|
627 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/Q/pi", "10", spool)); |
---|
628 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/Q/rho", "10", spool)); |
---|
629 | SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool)); |
---|
630 | svn_pool_clear(spool); |
---|
631 | |
---|
632 | /* Now, it's time to verify our results. */ |
---|
633 | { |
---|
634 | int j; |
---|
635 | /* Number, and list of, changed revisions for each path. Note |
---|
636 | that for now, bubble-up in directories causes the directory to |
---|
637 | appear changed though no entries were added or removed, and no |
---|
638 | property mods occurred. Also note that this matrix represents |
---|
639 | only the final state of the paths existing in HEAD of the |
---|
640 | repository. |
---|
641 | |
---|
642 | Notice for each revision, you can glance down that revision's |
---|
643 | column in this table and see all the paths modified directory or |
---|
644 | via bubble-up. */ |
---|
645 | static const struct revisions_changed_results test_data[25] = { |
---|
646 | /* path, num, revisions changed... */ |
---|
647 | { "", 11, { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } }, |
---|
648 | { "iota", 4, { 8, 4, 3, 1 } }, |
---|
649 | { "A", 10, { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 } }, |
---|
650 | { "A/mu", 3, { 5, 2, 1 } }, |
---|
651 | { "A/B", 5, { 5, 4, 3, 2, 1 } }, |
---|
652 | { "A/B/lambda", 2, { 3, 1 } }, |
---|
653 | { "A/B/E", 4, { 5, 4, 2, 1 } }, |
---|
654 | { "A/B/E/alpha", 3, { 5, 2, 1 } }, |
---|
655 | { "A/B/E/beta", 2, { 4, 1 } }, |
---|
656 | { "A/B/F", 1, { 1 } }, |
---|
657 | { "A/C", 1, { 1 } }, |
---|
658 | { "A/D", 10, { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 } }, |
---|
659 | { "A/D/gamma", 4, { 8, 6, 4, 1 } }, |
---|
660 | { "A/D/G", 6, { 8, 7, 6, 5, 4, 1 } }, |
---|
661 | { "A/D/G/pi", 5, { 8, 7, 6, 4, 1 } }, |
---|
662 | { "A/D/G/rho", 4, { 8, 6, 4, 1 } }, |
---|
663 | { "A/D/G/tau", 4, { 8, 6, 5, 1 } }, |
---|
664 | { "A/D/Q", 8, { 10, 9, 8, 7, 6, 5, 4, 1 } }, |
---|
665 | { "A/D/Q/pi", 7, { 10, 9, 8, 7, 6, 4, 1 } }, |
---|
666 | { "A/D/Q/rho", 6, { 10, 9, 8, 6, 4, 1 } }, |
---|
667 | { "A/D/Q/tau", 5, { 9, 8, 6, 5, 1 } }, |
---|
668 | { "A/D/H", 6, { 8, 6, 5, 3, 2, 1 } }, |
---|
669 | { "A/D/H/chi", 4, { 8, 6, 5, 1 } }, |
---|
670 | { "A/D/H/psi", 4, { 8, 6, 3, 1 } }, |
---|
671 | { "A/D/H/omega", 5, { 8, 6, 3, 2, 1 } } |
---|
672 | }; |
---|
673 | |
---|
674 | /* Now, for each path in the revision, get its changed-revisions |
---|
675 | array and compare the array to the static results above. */ |
---|
676 | for (j = 0; j < 25; j++) |
---|
677 | { |
---|
678 | int i; |
---|
679 | const char *path = test_data[j].path; |
---|
680 | int num_revs = test_data[j].num_revs; |
---|
681 | const svn_revnum_t *revs_changed = test_data[j].revs_changed; |
---|
682 | apr_array_header_t *revs = apr_array_make(spool, 10, |
---|
683 | sizeof(svn_revnum_t)); |
---|
684 | |
---|
685 | SVN_ERR(svn_repos_history(fs, path, history_to_revs_array, revs, |
---|
686 | 0, youngest_rev, TRUE, spool)); |
---|
687 | |
---|
688 | /* Are we at least looking at the right number of returned |
---|
689 | revisions? */ |
---|
690 | if ((! revs) || (revs->nelts != num_revs)) |
---|
691 | return svn_error_createf |
---|
692 | (SVN_ERR_FS_GENERAL, NULL, |
---|
693 | "Changed revisions differ from expected for '%s'\n%s", |
---|
694 | path, print_chrevs(revs, num_revs, revs_changed, spool)); |
---|
695 | |
---|
696 | /* Do the revisions lists match up exactly? */ |
---|
697 | for (i = 0; i < num_revs; i++) |
---|
698 | { |
---|
699 | svn_revnum_t rev = APR_ARRAY_IDX(revs, i, svn_revnum_t); |
---|
700 | if (rev != revs_changed[i]) |
---|
701 | return svn_error_createf |
---|
702 | (SVN_ERR_FS_GENERAL, NULL, |
---|
703 | "Changed revisions differ from expected for '%s'\n%s", |
---|
704 | path, print_chrevs(revs, num_revs, revs_changed, spool)); |
---|
705 | } |
---|
706 | |
---|
707 | /* Clear the per-iteration subpool. */ |
---|
708 | svn_pool_clear(spool); |
---|
709 | } |
---|
710 | } |
---|
711 | |
---|
712 | /* Destroy the subpool. */ |
---|
713 | svn_pool_destroy(spool); |
---|
714 | |
---|
715 | return SVN_NO_ERROR; |
---|
716 | } |
---|
717 | |
---|
718 | |
---|
719 | |
---|
720 | struct locations_info |
---|
721 | { |
---|
722 | svn_revnum_t rev; |
---|
723 | const char *path; |
---|
724 | }; |
---|
725 | |
---|
726 | /* Check that LOCATIONS contain everything in INFO and nothing more. */ |
---|
727 | static svn_error_t * |
---|
728 | check_locations_info(apr_hash_t *locations, const struct locations_info *info) |
---|
729 | { |
---|
730 | unsigned int i; |
---|
731 | for (i = 0; info->rev != 0; ++i, ++info) |
---|
732 | { |
---|
733 | const char *p = apr_hash_get(locations, &info->rev, sizeof |
---|
734 | (svn_revnum_t)); |
---|
735 | if (!p) |
---|
736 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
737 | "Missing path for revision %ld", info->rev); |
---|
738 | if (strcmp(p, info->path) != 0) |
---|
739 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
740 | "Pth mismatch for rev %ld", info->rev); |
---|
741 | } |
---|
742 | |
---|
743 | if (apr_hash_count(locations) > i) |
---|
744 | return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
---|
745 | "Returned locations contain too many elements."); |
---|
746 | |
---|
747 | return SVN_NO_ERROR; |
---|
748 | } |
---|
749 | |
---|
750 | /* Check that all locations in INFO exist in REPOS for PATH and PEG_REVISION. |
---|
751 | */ |
---|
752 | static svn_error_t * |
---|
753 | check_locations(svn_fs_t *fs, struct locations_info *info, |
---|
754 | const char *path, svn_revnum_t peg_revision, |
---|
755 | apr_pool_t *pool) |
---|
756 | { |
---|
757 | apr_array_header_t *a = apr_array_make(pool, 0, sizeof(svn_revnum_t)); |
---|
758 | apr_hash_t *h; |
---|
759 | struct locations_info *iter; |
---|
760 | |
---|
761 | for (iter = info; iter->rev != 0; ++iter) |
---|
762 | APR_ARRAY_PUSH(a, svn_revnum_t) = iter->rev; |
---|
763 | |
---|
764 | SVN_ERR(svn_repos_trace_node_locations(fs, &h, path, peg_revision, a, |
---|
765 | NULL, NULL, pool)); |
---|
766 | SVN_ERR(check_locations_info(h, info)); |
---|
767 | |
---|
768 | return SVN_NO_ERROR; |
---|
769 | } |
---|
770 | |
---|
771 | static svn_error_t * |
---|
772 | node_locations(const char **msg, |
---|
773 | svn_boolean_t msg_only, |
---|
774 | svn_test_opts_t *opts, |
---|
775 | apr_pool_t *pool) |
---|
776 | { |
---|
777 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
778 | svn_repos_t *repos; |
---|
779 | svn_fs_t *fs; |
---|
780 | svn_fs_txn_t *txn; |
---|
781 | svn_fs_root_t *txn_root, *root; |
---|
782 | svn_revnum_t youngest_rev; |
---|
783 | |
---|
784 | *msg = "test svn_repos_node_locations"; |
---|
785 | if (msg_only) |
---|
786 | return SVN_NO_ERROR; |
---|
787 | |
---|
788 | /* Create the repository with a Greek tree. */ |
---|
789 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations", |
---|
790 | opts, pool)); |
---|
791 | fs = svn_repos_fs(repos); |
---|
792 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
793 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
794 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
795 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
796 | svn_pool_clear(subpool); |
---|
797 | |
---|
798 | /* Move a file. Rev 2. */ |
---|
799 | SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool)); |
---|
800 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
801 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
802 | SVN_ERR(svn_fs_copy(root, "/A/mu", txn_root, "/mu.new", subpool)); |
---|
803 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
804 | { |
---|
805 | struct locations_info info[] = |
---|
806 | { |
---|
807 | { 1, "/A/mu" }, |
---|
808 | { 2, "/mu.new" }, |
---|
809 | { 0 } |
---|
810 | }; |
---|
811 | |
---|
812 | /* Test this twice, once with a leading slash, once without, |
---|
813 | because we know that the "without" form has caused us trouble |
---|
814 | in the past. */ |
---|
815 | SVN_ERR(check_locations(fs, info, "/mu.new", 2, pool)); |
---|
816 | SVN_ERR(check_locations(fs, info, "mu.new", 2, pool)); |
---|
817 | } |
---|
818 | svn_pool_clear(subpool); |
---|
819 | |
---|
820 | return SVN_NO_ERROR; |
---|
821 | } |
---|
822 | |
---|
823 | |
---|
824 | static svn_error_t * |
---|
825 | node_locations2(const char **msg, |
---|
826 | svn_boolean_t msg_only, |
---|
827 | svn_test_opts_t *opts, |
---|
828 | apr_pool_t *pool) |
---|
829 | { |
---|
830 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
831 | svn_repos_t *repos; |
---|
832 | svn_fs_t *fs; |
---|
833 | svn_fs_txn_t *txn; |
---|
834 | svn_fs_root_t *txn_root, *root; |
---|
835 | svn_revnum_t youngest_rev = 0; |
---|
836 | |
---|
837 | *msg = "test svn_repos_node_locations some more"; |
---|
838 | if (msg_only) |
---|
839 | return SVN_NO_ERROR; |
---|
840 | |
---|
841 | /* Create the repository. */ |
---|
842 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations2", |
---|
843 | opts, pool)); |
---|
844 | fs = svn_repos_fs(repos); |
---|
845 | |
---|
846 | /* Revision 1: Add a directory /foo */ |
---|
847 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
848 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
849 | SVN_ERR(svn_fs_make_dir(txn_root, "/foo", subpool)); |
---|
850 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
851 | svn_pool_clear(subpool); |
---|
852 | |
---|
853 | /* Revision 2: Copy /foo to /bar, and add /bar/baz */ |
---|
854 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
855 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
856 | SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool)); |
---|
857 | SVN_ERR(svn_fs_copy(root, "/foo", txn_root, "/bar", subpool)); |
---|
858 | SVN_ERR(svn_fs_make_file(txn_root, "/bar/baz", subpool)); |
---|
859 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
860 | svn_pool_clear(subpool); |
---|
861 | |
---|
862 | /* Revision 3: Modify /bar/baz */ |
---|
863 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
864 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
865 | SVN_ERR(svn_test__set_file_contents(txn_root, "/bar/baz", "brrt", subpool)); |
---|
866 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
867 | svn_pool_clear(subpool); |
---|
868 | |
---|
869 | /* Revision 4: Modify /bar/baz again */ |
---|
870 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
871 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
872 | SVN_ERR(svn_test__set_file_contents(txn_root, "/bar/baz", "bzzz", subpool)); |
---|
873 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
874 | svn_pool_clear(subpool); |
---|
875 | |
---|
876 | /* Now, check locations. */ |
---|
877 | { |
---|
878 | struct locations_info info[] = |
---|
879 | { |
---|
880 | { 3, "/bar/baz" }, |
---|
881 | { 2, "/bar/baz" }, |
---|
882 | { 0 } |
---|
883 | }; |
---|
884 | SVN_ERR(check_locations(fs, info, "/bar/baz", youngest_rev, pool)); |
---|
885 | } |
---|
886 | |
---|
887 | return SVN_NO_ERROR; |
---|
888 | } |
---|
889 | |
---|
890 | |
---|
891 | |
---|
892 | /* Testing the reporter. */ |
---|
893 | |
---|
894 | /* Functions for an editor that will catch removal of defunct locks. */ |
---|
895 | |
---|
896 | /* The main editor baton. */ |
---|
897 | typedef struct rmlocks_baton_t { |
---|
898 | apr_hash_t *removed; |
---|
899 | apr_pool_t *pool; |
---|
900 | } rmlocks_baton_t; |
---|
901 | |
---|
902 | /* The file baton. */ |
---|
903 | typedef struct rmlocks_file_baton_t { |
---|
904 | rmlocks_baton_t *main_baton; |
---|
905 | const char *path; |
---|
906 | } rmlocks_file_baton_t; |
---|
907 | |
---|
908 | /* An svn_delta_editor_t function. */ |
---|
909 | static svn_error_t * |
---|
910 | rmlocks_open_file(const char *path, |
---|
911 | void *parent_baton, |
---|
912 | svn_revnum_t base_revision, |
---|
913 | apr_pool_t *file_pool, |
---|
914 | void **file_baton) |
---|
915 | { |
---|
916 | rmlocks_file_baton_t *fb = apr_palloc(file_pool, sizeof(*fb)); |
---|
917 | rmlocks_baton_t *b = parent_baton; |
---|
918 | |
---|
919 | fb->main_baton = b; |
---|
920 | fb->path = apr_pstrdup(b->pool, path); |
---|
921 | |
---|
922 | *file_baton = fb; |
---|
923 | |
---|
924 | return SVN_NO_ERROR; |
---|
925 | } |
---|
926 | |
---|
927 | /* An svn_delta_editor_t function. */ |
---|
928 | static svn_error_t * |
---|
929 | rmlocks_change_prop(void *file_baton, |
---|
930 | const char *name, |
---|
931 | const svn_string_t *value, |
---|
932 | apr_pool_t *pool) |
---|
933 | { |
---|
934 | rmlocks_file_baton_t *fb = file_baton; |
---|
935 | |
---|
936 | if (strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) == 0) |
---|
937 | { |
---|
938 | if (value != NULL) |
---|
939 | return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
---|
940 | "Value for lock-token property not NULL"); |
---|
941 | |
---|
942 | /* We only want it removed once. */ |
---|
943 | if (apr_hash_get(fb->main_baton->removed, fb->path, |
---|
944 | APR_HASH_KEY_STRING) != NULL) |
---|
945 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
946 | "Lock token for '%s' already removed", |
---|
947 | fb->path); |
---|
948 | |
---|
949 | /* Mark as removed. */ |
---|
950 | apr_hash_set(fb->main_baton->removed, fb->path, APR_HASH_KEY_STRING, |
---|
951 | (void *)1); |
---|
952 | } |
---|
953 | |
---|
954 | return SVN_NO_ERROR; |
---|
955 | } |
---|
956 | |
---|
957 | /* An svn_delta_editor_t function. */ |
---|
958 | static svn_error_t * |
---|
959 | rmlocks_open_root(void *edit_baton, |
---|
960 | svn_revnum_t base_revision, |
---|
961 | apr_pool_t *dir_pool, |
---|
962 | void **root_baton) |
---|
963 | { |
---|
964 | *root_baton = edit_baton; |
---|
965 | return SVN_NO_ERROR; |
---|
966 | } |
---|
967 | |
---|
968 | /* An svn_delta_editor_t function. */ |
---|
969 | static svn_error_t * |
---|
970 | rmlocks_open_directory(const char *path, |
---|
971 | void *parent_baton, |
---|
972 | svn_revnum_t base_revision, |
---|
973 | apr_pool_t *pool, |
---|
974 | void **dir_baton) |
---|
975 | { |
---|
976 | *dir_baton = parent_baton; |
---|
977 | return SVN_NO_ERROR; |
---|
978 | } |
---|
979 | |
---|
980 | /* Create an svn_delta_editor/baton, storing them in EDITOR/EDIT_BATON, |
---|
981 | that will store paths for which lock tokens were *REMOVED in REMOVED. |
---|
982 | Allocate the editor and *REMOVED in POOL. */ |
---|
983 | static svn_error_t * |
---|
984 | create_rmlocks_editor(svn_delta_editor_t **editor, |
---|
985 | void **edit_baton, |
---|
986 | apr_hash_t **removed, |
---|
987 | apr_pool_t *pool) |
---|
988 | { |
---|
989 | rmlocks_baton_t *baton = apr_palloc(pool, sizeof(*baton)); |
---|
990 | |
---|
991 | /* Create the editor. */ |
---|
992 | *editor = svn_delta_default_editor(pool); |
---|
993 | (*editor)->open_root = rmlocks_open_root; |
---|
994 | (*editor)->open_directory = rmlocks_open_directory; |
---|
995 | (*editor)->open_file = rmlocks_open_file; |
---|
996 | (*editor)->change_file_prop = rmlocks_change_prop; |
---|
997 | |
---|
998 | /* Initialize the baton. */ |
---|
999 | baton->removed = apr_hash_make(pool); |
---|
1000 | baton->pool = pool; |
---|
1001 | *edit_baton = baton; |
---|
1002 | |
---|
1003 | *removed = baton->removed; |
---|
1004 | |
---|
1005 | return SVN_NO_ERROR; |
---|
1006 | } |
---|
1007 | |
---|
1008 | /* Check that HASH contains exactly the const char * entries for all entries |
---|
1009 | in the NULL-terminated array SPEC. */ |
---|
1010 | static svn_error_t * |
---|
1011 | rmlocks_check(const char **spec, apr_hash_t *hash) |
---|
1012 | { |
---|
1013 | apr_size_t n = 0; |
---|
1014 | |
---|
1015 | for (; *spec; ++spec, ++n) |
---|
1016 | { |
---|
1017 | if (! apr_hash_get(hash, *spec, APR_HASH_KEY_STRING)) |
---|
1018 | return svn_error_createf |
---|
1019 | (SVN_ERR_TEST_FAILED, NULL, |
---|
1020 | "Lock token for '%s' should have been removed", *spec); |
---|
1021 | } |
---|
1022 | |
---|
1023 | if (n < apr_hash_count(hash)) |
---|
1024 | return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
---|
1025 | "Lock token for one or more paths unexpectedly " |
---|
1026 | "removed"); |
---|
1027 | return SVN_NO_ERROR; |
---|
1028 | } |
---|
1029 | |
---|
1030 | /* Test that defunct locks are removed by the reporter. */ |
---|
1031 | static svn_error_t * |
---|
1032 | rmlocks(const char **msg, |
---|
1033 | svn_boolean_t msg_only, |
---|
1034 | svn_test_opts_t *opts, |
---|
1035 | apr_pool_t *pool) |
---|
1036 | { |
---|
1037 | svn_repos_t *repos; |
---|
1038 | svn_fs_t *fs; |
---|
1039 | svn_fs_txn_t *txn; |
---|
1040 | svn_fs_root_t *txn_root; |
---|
1041 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1042 | svn_revnum_t youngest_rev; |
---|
1043 | svn_delta_editor_t *editor; |
---|
1044 | void *edit_baton, *report_baton; |
---|
1045 | svn_lock_t *l1, *l2, *l3, *l4; |
---|
1046 | svn_fs_access_t *fs_access; |
---|
1047 | apr_hash_t *removed; |
---|
1048 | |
---|
1049 | *msg = "test removal of defunct locks"; |
---|
1050 | |
---|
1051 | if (msg_only) |
---|
1052 | return SVN_NO_ERROR; |
---|
1053 | |
---|
1054 | /* Create a filesystem and repository. */ |
---|
1055 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-rmlocks", |
---|
1056 | opts, pool)); |
---|
1057 | fs = svn_repos_fs(repos); |
---|
1058 | |
---|
1059 | /* Prepare a txn to receive the greek tree. */ |
---|
1060 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
1061 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1062 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
1063 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1064 | svn_pool_clear(subpool); |
---|
1065 | |
---|
1066 | SVN_ERR(svn_fs_create_access(&fs_access, "user1", pool)); |
---|
1067 | SVN_ERR(svn_fs_set_access(fs, fs_access)); |
---|
1068 | |
---|
1069 | /* Lock some files, break a lock, steal another and check that those get |
---|
1070 | removed. */ |
---|
1071 | { |
---|
1072 | const char *expected [] = { "A/mu", "A/D/gamma", NULL }; |
---|
1073 | |
---|
1074 | SVN_ERR(svn_fs_lock(&l1, fs, "/iota", NULL, NULL, 0, 0, youngest_rev, |
---|
1075 | FALSE, subpool)); |
---|
1076 | SVN_ERR(svn_fs_lock(&l2, fs, "/A/mu", NULL, NULL, 0, 0, youngest_rev, |
---|
1077 | FALSE, subpool)); |
---|
1078 | SVN_ERR(svn_fs_lock(&l3, fs, "/A/D/gamma", NULL, NULL, 0, 0, youngest_rev, |
---|
1079 | FALSE, subpool)); |
---|
1080 | |
---|
1081 | /* Break l2. */ |
---|
1082 | SVN_ERR(svn_fs_unlock(fs, "/A/mu", NULL, TRUE, subpool)); |
---|
1083 | |
---|
1084 | /* Steal l3 from ourselves. */ |
---|
1085 | SVN_ERR(svn_fs_lock(&l4, fs, "/A/D/gamma", NULL, NULL, 0, 0, youngest_rev, |
---|
1086 | TRUE, subpool)); |
---|
1087 | |
---|
1088 | /* Create the editor. */ |
---|
1089 | SVN_ERR(create_rmlocks_editor(&editor, &edit_baton, &removed, subpool)); |
---|
1090 | |
---|
1091 | /* Report what we have. */ |
---|
1092 | SVN_ERR(svn_repos_begin_report2(&report_baton, 1, repos, "/", "", NULL, |
---|
1093 | FALSE, svn_depth_infinity, FALSE, FALSE, |
---|
1094 | editor, edit_baton, NULL, NULL, subpool)); |
---|
1095 | SVN_ERR(svn_repos_set_path3(report_baton, "", 1, |
---|
1096 | svn_depth_infinity, |
---|
1097 | FALSE, NULL, subpool)); |
---|
1098 | SVN_ERR(svn_repos_set_path3(report_baton, "iota", 1, |
---|
1099 | svn_depth_infinity, |
---|
1100 | FALSE, l1->token, subpool)); |
---|
1101 | SVN_ERR(svn_repos_set_path3(report_baton, "A/mu", 1, |
---|
1102 | svn_depth_infinity, |
---|
1103 | FALSE, l2->token, subpool)); |
---|
1104 | SVN_ERR(svn_repos_set_path3(report_baton, "A/D/gamma", 1, |
---|
1105 | svn_depth_infinity, |
---|
1106 | FALSE, l3->token, subpool)); |
---|
1107 | |
---|
1108 | /* End the report. */ |
---|
1109 | SVN_ERR(svn_repos_finish_report(report_baton, pool)); |
---|
1110 | |
---|
1111 | /* And check that the edit did what we wanted. */ |
---|
1112 | SVN_ERR(rmlocks_check(expected, removed)); |
---|
1113 | } |
---|
1114 | |
---|
1115 | svn_pool_destroy(subpool); |
---|
1116 | |
---|
1117 | return SVN_NO_ERROR; |
---|
1118 | } |
---|
1119 | |
---|
1120 | |
---|
1121 | |
---|
1122 | /* Helper for the authz test. Set *AUTHZ_P to a representation of |
---|
1123 | AUTHZ_CONTENTS, using POOL for temporary allocation. */ |
---|
1124 | static svn_error_t * |
---|
1125 | authz_get_handle(svn_authz_t **authz_p, const char *authz_contents, |
---|
1126 | apr_pool_t *pool) |
---|
1127 | { |
---|
1128 | const char *authz_file_path; |
---|
1129 | |
---|
1130 | /* Create a temporary file. */ |
---|
1131 | SVN_ERR_W(svn_io_write_unique(&authz_file_path, NULL, |
---|
1132 | authz_contents, strlen(authz_contents), |
---|
1133 | svn_io_file_del_on_pool_cleanup, pool), |
---|
1134 | "Writing temporary authz file"); |
---|
1135 | |
---|
1136 | /* Read the authz configuration back and start testing. */ |
---|
1137 | SVN_ERR_W(svn_repos_authz_read(authz_p, authz_file_path, TRUE, pool), |
---|
1138 | "Opening test authz file"); |
---|
1139 | |
---|
1140 | /* Done with the file. */ |
---|
1141 | SVN_ERR_W(svn_io_remove_file(authz_file_path, pool), |
---|
1142 | "Removing test authz file"); |
---|
1143 | |
---|
1144 | return SVN_NO_ERROR; |
---|
1145 | } |
---|
1146 | |
---|
1147 | |
---|
1148 | |
---|
1149 | /* Test that authz is giving out the right authorizations. */ |
---|
1150 | static svn_error_t * |
---|
1151 | authz(const char **msg, |
---|
1152 | svn_boolean_t msg_only, |
---|
1153 | svn_test_opts_t *opts, |
---|
1154 | apr_pool_t *pool) |
---|
1155 | { |
---|
1156 | const char *contents; |
---|
1157 | svn_authz_t *authz_cfg; |
---|
1158 | svn_error_t *err; |
---|
1159 | svn_boolean_t access_granted; |
---|
1160 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1161 | int i; |
---|
1162 | /* Definition of the paths to test and expected replies for each. */ |
---|
1163 | struct |
---|
1164 | { |
---|
1165 | const char *path; |
---|
1166 | const char *user; |
---|
1167 | const svn_repos_authz_access_t required; |
---|
1168 | const svn_boolean_t expected; |
---|
1169 | } test_set[] = { |
---|
1170 | /* Test that read rules are correctly used. */ |
---|
1171 | { "/A", NULL, svn_authz_read, TRUE }, |
---|
1172 | { "/iota", NULL, svn_authz_read, FALSE }, |
---|
1173 | /* Test that write rules are correctly used. */ |
---|
1174 | { "/A", "plato", svn_authz_write, TRUE }, |
---|
1175 | { "/A", NULL, svn_authz_write, FALSE }, |
---|
1176 | /* Test that pan-repository rules are found and used. */ |
---|
1177 | { "/A/B/lambda", "plato", svn_authz_read, TRUE }, |
---|
1178 | { "/A/B/lambda", NULL, svn_authz_read, FALSE }, |
---|
1179 | /* Test that authz uses parent path ACLs if no rule for the path |
---|
1180 | exists. */ |
---|
1181 | { "/A/C", NULL, svn_authz_read, TRUE }, |
---|
1182 | /* Test that recursive access requests take into account the rules |
---|
1183 | of subpaths. */ |
---|
1184 | { "/A/D", "plato", svn_authz_read | svn_authz_recursive, TRUE }, |
---|
1185 | { "/A/D", NULL, svn_authz_read | svn_authz_recursive, FALSE }, |
---|
1186 | /* Test global write access lookups. */ |
---|
1187 | { NULL, "plato", svn_authz_read, TRUE }, |
---|
1188 | { NULL, NULL, svn_authz_write, FALSE }, |
---|
1189 | /* Sentinel */ |
---|
1190 | { NULL, NULL, svn_authz_none, FALSE } |
---|
1191 | }; |
---|
1192 | |
---|
1193 | *msg = "test authz access control"; |
---|
1194 | |
---|
1195 | if (msg_only) |
---|
1196 | return SVN_NO_ERROR; |
---|
1197 | |
---|
1198 | /* The test logic: |
---|
1199 | * |
---|
1200 | * 1. Perform various access tests on a set of authz rules. Each |
---|
1201 | * test has a known outcome and tests different aspects of authz, |
---|
1202 | * such as inheriting parent-path authz, pan-repository rules or |
---|
1203 | * recursive access. 'plato' is our friendly neighborhood user with |
---|
1204 | * more access rights than other anonymous philosophers. |
---|
1205 | * |
---|
1206 | * 2. Load an authz file containing a cyclic dependency in groups |
---|
1207 | * and another containing a reference to an undefined group. Verify |
---|
1208 | * that svn_repos_authz_read fails to load both and returns an |
---|
1209 | * "invalid configuration" error. |
---|
1210 | * |
---|
1211 | * 3. Regression test for a bug in how recursion is handled in |
---|
1212 | * authz. The bug was that paths not under the parent path |
---|
1213 | * requested were being considered during the determination of |
---|
1214 | * access rights (eg. a rule for /dir2 matched during a lookup for |
---|
1215 | * /dir), due to incomplete tests on path relations. |
---|
1216 | */ |
---|
1217 | |
---|
1218 | /* The authz rules for the phase 1 tests. */ |
---|
1219 | contents = |
---|
1220 | "[greek:/A]" NL |
---|
1221 | "* = r" NL |
---|
1222 | "plato = w" NL |
---|
1223 | "" NL |
---|
1224 | "[greek:/iota]" NL |
---|
1225 | "* =" NL |
---|
1226 | "" NL |
---|
1227 | "[/A/B/lambda]" NL |
---|
1228 | "plato = r" NL |
---|
1229 | "* =" NL |
---|
1230 | "" NL |
---|
1231 | "[greek:/A/D]" NL |
---|
1232 | "plato = r" NL |
---|
1233 | "* = r" NL |
---|
1234 | "" NL |
---|
1235 | "[greek:/A/D/G]" NL |
---|
1236 | "plato = r" NL |
---|
1237 | "* =" NL |
---|
1238 | "" NL |
---|
1239 | "[greek:/A/B/E/beta]" NL |
---|
1240 | "* =" NL |
---|
1241 | "" NL |
---|
1242 | "[/nowhere]" NL |
---|
1243 | "nobody = r" NL |
---|
1244 | "" NL; |
---|
1245 | |
---|
1246 | /* Load the test authz rules. */ |
---|
1247 | SVN_ERR(authz_get_handle(&authz_cfg, contents, subpool)); |
---|
1248 | |
---|
1249 | /* Loop over the test array and test each case. */ |
---|
1250 | for (i = 0; !(test_set[i].path == NULL |
---|
1251 | && test_set[i].required == svn_authz_none); i++) |
---|
1252 | { |
---|
1253 | SVN_ERR(svn_repos_authz_check_access(authz_cfg, "greek", |
---|
1254 | test_set[i].path, |
---|
1255 | test_set[i].user, |
---|
1256 | test_set[i].required, |
---|
1257 | &access_granted, subpool)); |
---|
1258 | |
---|
1259 | if (access_granted != test_set[i].expected) |
---|
1260 | { |
---|
1261 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
1262 | "Authz incorrectly %s %s%s access " |
---|
1263 | "to greek:%s for user %s", |
---|
1264 | access_granted ? |
---|
1265 | "grants" : "denies", |
---|
1266 | test_set[i].required |
---|
1267 | & svn_authz_recursive ? |
---|
1268 | "recursive " : "", |
---|
1269 | test_set[i].required |
---|
1270 | & svn_authz_read ? |
---|
1271 | "read" : "write", |
---|
1272 | test_set[i].path, |
---|
1273 | test_set[i].user ? |
---|
1274 | test_set[i].user : "-"); |
---|
1275 | } |
---|
1276 | } |
---|
1277 | |
---|
1278 | |
---|
1279 | /* The authz rules for the phase 2 tests, first case (cyclic |
---|
1280 | dependency). */ |
---|
1281 | contents = |
---|
1282 | "[groups]" NL |
---|
1283 | "slaves = cooks,scribes,@gladiators" NL |
---|
1284 | "gladiators = equites,thraces,@slaves" NL |
---|
1285 | "" NL |
---|
1286 | "[greek:/A]" NL |
---|
1287 | "@slaves = r" NL; |
---|
1288 | |
---|
1289 | /* Load the test authz rules and check that group cycles are |
---|
1290 | reported. */ |
---|
1291 | err = authz_get_handle(&authz_cfg, contents, subpool); |
---|
1292 | if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) |
---|
1293 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1294 | "Got %s error instead of expected " |
---|
1295 | "SVN_ERR_AUTHZ_INVALID_CONFIG", |
---|
1296 | err ? "unexpected" : "no"); |
---|
1297 | svn_error_clear(err); |
---|
1298 | |
---|
1299 | /* The authz rules for the phase 2 tests, second case (missing group |
---|
1300 | definition). */ |
---|
1301 | contents = |
---|
1302 | "[greek:/A]" NL |
---|
1303 | "@senate = r" NL; |
---|
1304 | |
---|
1305 | /* Check that references to undefined groups are reported. */ |
---|
1306 | err = authz_get_handle(&authz_cfg, contents, subpool); |
---|
1307 | if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) |
---|
1308 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1309 | "Got %s error instead of expected " |
---|
1310 | "SVN_ERR_AUTHZ_INVALID_CONFIG", |
---|
1311 | err ? "unexpected" : "no"); |
---|
1312 | svn_error_clear(err); |
---|
1313 | |
---|
1314 | /* The authz rules for the phase 3 tests */ |
---|
1315 | contents = |
---|
1316 | "[/]" NL |
---|
1317 | "* = rw" NL |
---|
1318 | "" NL |
---|
1319 | "[greek:/dir2/secret]" NL |
---|
1320 | "* =" NL; |
---|
1321 | |
---|
1322 | /* Load the test authz rules. */ |
---|
1323 | SVN_ERR(authz_get_handle(&authz_cfg, contents, subpool)); |
---|
1324 | |
---|
1325 | /* Verify that the rule on /dir2/secret doesn't affect this |
---|
1326 | request */ |
---|
1327 | SVN_ERR(svn_repos_authz_check_access(authz_cfg, "greek", |
---|
1328 | "/dir", NULL, |
---|
1329 | (svn_authz_read |
---|
1330 | | svn_authz_recursive), |
---|
1331 | &access_granted, subpool)); |
---|
1332 | if (!access_granted) |
---|
1333 | return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
---|
1334 | "Regression: incomplete ancestry test " |
---|
1335 | "for recursive access lookup."); |
---|
1336 | |
---|
1337 | /* That's a wrap! */ |
---|
1338 | svn_pool_destroy(subpool); |
---|
1339 | return SVN_NO_ERROR; |
---|
1340 | } |
---|
1341 | |
---|
1342 | |
---|
1343 | |
---|
1344 | /* Callback for the commit editor tests that relays requests to |
---|
1345 | authz. */ |
---|
1346 | static svn_error_t * |
---|
1347 | commit_authz_cb(svn_repos_authz_access_t required, |
---|
1348 | svn_boolean_t *allowed, |
---|
1349 | svn_fs_root_t *root, |
---|
1350 | const char *path, |
---|
1351 | void *baton, |
---|
1352 | apr_pool_t *pool) |
---|
1353 | { |
---|
1354 | svn_authz_t *authz_file = baton; |
---|
1355 | |
---|
1356 | return svn_repos_authz_check_access(authz_file, "test", path, |
---|
1357 | "plato", required, allowed, |
---|
1358 | pool); |
---|
1359 | } |
---|
1360 | |
---|
1361 | |
---|
1362 | |
---|
1363 | /* Test that the commit editor is taking authz into account |
---|
1364 | properly */ |
---|
1365 | static svn_error_t * |
---|
1366 | commit_editor_authz(const char **msg, |
---|
1367 | svn_boolean_t msg_only, |
---|
1368 | svn_test_opts_t *opts, |
---|
1369 | apr_pool_t *pool) |
---|
1370 | { |
---|
1371 | svn_repos_t *repos; |
---|
1372 | svn_fs_t *fs; |
---|
1373 | svn_fs_txn_t *txn; |
---|
1374 | svn_fs_root_t *txn_root; |
---|
1375 | svn_revnum_t youngest_rev; |
---|
1376 | void *edit_baton; |
---|
1377 | void *root_baton, *dir_baton, *dir2_baton, *file_baton; |
---|
1378 | svn_error_t *err; |
---|
1379 | const svn_delta_editor_t *editor; |
---|
1380 | svn_authz_t *authz_file; |
---|
1381 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1382 | const char *authz_contents; |
---|
1383 | |
---|
1384 | *msg = "test authz in the commit editor"; |
---|
1385 | |
---|
1386 | if (msg_only) |
---|
1387 | return SVN_NO_ERROR; |
---|
1388 | |
---|
1389 | /* The Test Plan |
---|
1390 | * |
---|
1391 | * We create a greek tree repository, then create a commit editor |
---|
1392 | * and try to perform various operations that will run into authz |
---|
1393 | * callbacks. Check that all operations are properly |
---|
1394 | * authorized/denied when necessary. We don't try to be exhaustive |
---|
1395 | * in the kinds of authz lookups. We just make sure that the editor |
---|
1396 | * replies to the calls in a way that proves it is doing authz |
---|
1397 | * lookups. |
---|
1398 | * |
---|
1399 | * Note that this use of the commit editor is not kosher according |
---|
1400 | * to the generic editor API (we aren't allowed to continue editing |
---|
1401 | * after an error, nor are we allowed to assume that errors are |
---|
1402 | * returned by the operations which caused them). But it should |
---|
1403 | * work fine with this particular editor implementation. |
---|
1404 | */ |
---|
1405 | |
---|
1406 | /* Create a filesystem and repository. */ |
---|
1407 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-authz", |
---|
1408 | opts, subpool)); |
---|
1409 | fs = svn_repos_fs(repos); |
---|
1410 | |
---|
1411 | /* Prepare a txn to receive the greek tree. */ |
---|
1412 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
1413 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1414 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
1415 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1416 | |
---|
1417 | /* Load the authz rules for the greek tree. */ |
---|
1418 | authz_contents = |
---|
1419 | "" NL |
---|
1420 | "" NL |
---|
1421 | "[/]" NL |
---|
1422 | "plato = r" NL |
---|
1423 | "" NL |
---|
1424 | "[/A]" NL |
---|
1425 | "plato = rw" NL |
---|
1426 | "" NL |
---|
1427 | "[/A/alpha]" NL |
---|
1428 | "plato = " NL |
---|
1429 | "" NL |
---|
1430 | "[/A/C]" NL |
---|
1431 | "" NL |
---|
1432 | "plato = " NL |
---|
1433 | "" NL |
---|
1434 | "[/A/D]" NL |
---|
1435 | "plato = rw" NL |
---|
1436 | "" NL |
---|
1437 | "[/A/D/G]" NL |
---|
1438 | "plato = r"; /* No newline at end of file. */ |
---|
1439 | |
---|
1440 | SVN_ERR(authz_get_handle(&authz_file, authz_contents, subpool)); |
---|
1441 | |
---|
1442 | /* Create a new commit editor in which we're going to play with |
---|
1443 | authz */ |
---|
1444 | SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos, |
---|
1445 | NULL, "file://test", "/", |
---|
1446 | "plato", "test commit", NULL, |
---|
1447 | NULL, commit_authz_cb, authz_file, |
---|
1448 | subpool)); |
---|
1449 | |
---|
1450 | /* Start fiddling. First get the root, which is readonly. All |
---|
1451 | write operations fail because of the root's permissions. */ |
---|
1452 | SVN_ERR(editor->open_root(edit_baton, 1, subpool, &root_baton)); |
---|
1453 | |
---|
1454 | /* Test denied file deletion. */ |
---|
1455 | err = editor->delete_entry("/iota", SVN_INVALID_REVNUM, root_baton, subpool); |
---|
1456 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1457 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1458 | "Got %s error instead of expected " |
---|
1459 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1460 | err ? "unexpected" : "no"); |
---|
1461 | svn_error_clear(err); |
---|
1462 | |
---|
1463 | /* Test authorized file open. */ |
---|
1464 | SVN_ERR(editor->open_file("/iota", root_baton, SVN_INVALID_REVNUM, |
---|
1465 | subpool, &file_baton)); |
---|
1466 | |
---|
1467 | /* Test unauthorized file prop set. */ |
---|
1468 | err = editor->change_file_prop(file_baton, "svn:test", |
---|
1469 | svn_string_create("test", subpool), |
---|
1470 | subpool); |
---|
1471 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1472 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1473 | "Got %s error instead of expected " |
---|
1474 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1475 | err ? "unexpected" : "no"); |
---|
1476 | svn_error_clear(err); |
---|
1477 | |
---|
1478 | /* Test denied file addition. */ |
---|
1479 | err = editor->add_file("/alpha", root_baton, NULL, SVN_INVALID_REVNUM, |
---|
1480 | subpool, &file_baton); |
---|
1481 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1482 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1483 | "Got %s error instead of expected " |
---|
1484 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1485 | err ? "unexpected" : "no"); |
---|
1486 | svn_error_clear(err); |
---|
1487 | |
---|
1488 | /* Test denied file copy. */ |
---|
1489 | err = editor->add_file("/alpha", root_baton, "file://test/A/B/lambda", |
---|
1490 | youngest_rev, subpool, &file_baton); |
---|
1491 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1492 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1493 | "Got %s error instead of expected " |
---|
1494 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1495 | err ? "unexpected" : "no"); |
---|
1496 | svn_error_clear(err); |
---|
1497 | |
---|
1498 | /* Test denied directory addition. */ |
---|
1499 | err = editor->add_directory("/I", root_baton, NULL, |
---|
1500 | SVN_INVALID_REVNUM, subpool, &dir_baton); |
---|
1501 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1502 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1503 | "Got %s error instead of expected " |
---|
1504 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1505 | err ? "unexpected" : "no"); |
---|
1506 | svn_error_clear(err); |
---|
1507 | |
---|
1508 | /* Test denied directory copy. */ |
---|
1509 | err = editor->add_directory("/J", root_baton, "file://test/A/D", |
---|
1510 | youngest_rev, subpool, &dir_baton); |
---|
1511 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1512 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1513 | "Got %s error instead of expected " |
---|
1514 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1515 | err ? "unexpected" : "no"); |
---|
1516 | svn_error_clear(err); |
---|
1517 | |
---|
1518 | /* Open directory /A, to which we have read/write access. */ |
---|
1519 | SVN_ERR(editor->open_directory("/A", root_baton, |
---|
1520 | SVN_INVALID_REVNUM, |
---|
1521 | subpool, &dir_baton)); |
---|
1522 | |
---|
1523 | /* Test denied file addition. Denied because of a conflicting rule |
---|
1524 | on the file path itself. */ |
---|
1525 | err = editor->add_file("/A/alpha", dir_baton, NULL, |
---|
1526 | SVN_INVALID_REVNUM, subpool, &file_baton); |
---|
1527 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1528 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1529 | "Got %s error instead of expected " |
---|
1530 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1531 | err ? "unexpected" : "no"); |
---|
1532 | svn_error_clear(err); |
---|
1533 | |
---|
1534 | /* Test authorized file addition. */ |
---|
1535 | SVN_ERR(editor->add_file("/A/B/theta", dir_baton, NULL, |
---|
1536 | SVN_INVALID_REVNUM, subpool, |
---|
1537 | &file_baton)); |
---|
1538 | |
---|
1539 | /* Test authorized file deletion. */ |
---|
1540 | SVN_ERR(editor->delete_entry("/A/mu", SVN_INVALID_REVNUM, dir_baton, |
---|
1541 | subpool)); |
---|
1542 | |
---|
1543 | /* Test authorized directory creation. */ |
---|
1544 | SVN_ERR(editor->add_directory("/A/E", dir_baton, NULL, |
---|
1545 | SVN_INVALID_REVNUM, subpool, |
---|
1546 | &dir2_baton)); |
---|
1547 | |
---|
1548 | /* Test authorized copy of a tree. */ |
---|
1549 | SVN_ERR(editor->add_directory("/A/J", dir_baton, "file://test/A/D", |
---|
1550 | youngest_rev, subpool, |
---|
1551 | &dir2_baton)); |
---|
1552 | |
---|
1553 | /* Open /A/D. This should be granted. */ |
---|
1554 | SVN_ERR(editor->open_directory("/A/D", dir_baton, SVN_INVALID_REVNUM, |
---|
1555 | subpool, &dir_baton)); |
---|
1556 | |
---|
1557 | /* Test denied recursive deletion. */ |
---|
1558 | err = editor->delete_entry("/A/D/G", SVN_INVALID_REVNUM, dir_baton, |
---|
1559 | subpool); |
---|
1560 | if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) |
---|
1561 | return svn_error_createf(SVN_ERR_TEST_FAILED, err, |
---|
1562 | "Got %s error instead of expected " |
---|
1563 | "SVN_ERR_AUTHZ_UNWRITABLE", |
---|
1564 | err ? "unexpected" : "no"); |
---|
1565 | svn_error_clear(err); |
---|
1566 | |
---|
1567 | /* Test authorized recursive deletion. */ |
---|
1568 | SVN_ERR(editor->delete_entry("/A/D/H", SVN_INVALID_REVNUM, |
---|
1569 | dir_baton, subpool)); |
---|
1570 | |
---|
1571 | /* Test authorized propset (open the file first). */ |
---|
1572 | SVN_ERR(editor->open_file("/A/D/gamma", dir_baton, SVN_INVALID_REVNUM, |
---|
1573 | subpool, &file_baton)); |
---|
1574 | SVN_ERR(editor->change_file_prop(file_baton, "svn:test", |
---|
1575 | svn_string_create("test", subpool), |
---|
1576 | subpool)); |
---|
1577 | |
---|
1578 | /* Done. */ |
---|
1579 | SVN_ERR(editor->abort_edit(edit_baton, subpool)); |
---|
1580 | svn_pool_destroy(subpool); |
---|
1581 | |
---|
1582 | return SVN_NO_ERROR; |
---|
1583 | } |
---|
1584 | |
---|
1585 | /* This implements svn_commit_callback2_t. */ |
---|
1586 | static svn_error_t * |
---|
1587 | dummy_commit_cb(const svn_commit_info_t *commit_info, |
---|
1588 | void *baton, apr_pool_t *pool) |
---|
1589 | { |
---|
1590 | return SVN_NO_ERROR; |
---|
1591 | } |
---|
1592 | |
---|
1593 | /* Test using explicit txns during a commit. */ |
---|
1594 | static svn_error_t * |
---|
1595 | commit_continue_txn(const char **msg, |
---|
1596 | svn_boolean_t msg_only, |
---|
1597 | svn_test_opts_t *opts, |
---|
1598 | apr_pool_t *pool) |
---|
1599 | { |
---|
1600 | svn_repos_t *repos; |
---|
1601 | svn_fs_t *fs; |
---|
1602 | svn_fs_txn_t *txn; |
---|
1603 | svn_fs_root_t *txn_root, *revision_root; |
---|
1604 | svn_revnum_t youngest_rev; |
---|
1605 | void *edit_baton; |
---|
1606 | void *root_baton, *file_baton; |
---|
1607 | const svn_delta_editor_t *editor; |
---|
1608 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1609 | const char *txn_name; |
---|
1610 | |
---|
1611 | *msg = "test commit with explicit txn"; |
---|
1612 | |
---|
1613 | if (msg_only) |
---|
1614 | return SVN_NO_ERROR; |
---|
1615 | |
---|
1616 | /* The Test Plan |
---|
1617 | * |
---|
1618 | * We create a greek tree repository, then create a transaction and |
---|
1619 | * a commit editor from that txn. We do one change, abort the edit, reopen |
---|
1620 | * the txn and create a new commit editor, do anyther change and commit. |
---|
1621 | * We check that both changes were done. |
---|
1622 | */ |
---|
1623 | |
---|
1624 | /* Create a filesystem and repository. */ |
---|
1625 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-continue", |
---|
1626 | opts, subpool)); |
---|
1627 | fs = svn_repos_fs(repos); |
---|
1628 | |
---|
1629 | /* Prepare a txn to receive the greek tree. */ |
---|
1630 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
1631 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1632 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
1633 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1634 | |
---|
1635 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
1636 | SVN_ERR(svn_fs_txn_name(&txn_name, txn, subpool)); |
---|
1637 | SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos, |
---|
1638 | txn, "file://test", "/", |
---|
1639 | "plato", "test commit", |
---|
1640 | dummy_commit_cb, NULL, NULL, NULL, |
---|
1641 | subpool)); |
---|
1642 | |
---|
1643 | SVN_ERR(editor->open_root(edit_baton, 1, subpool, &root_baton)); |
---|
1644 | |
---|
1645 | SVN_ERR(editor->add_file("/f1", root_baton, NULL, SVN_INVALID_REVNUM, |
---|
1646 | subpool, &file_baton)); |
---|
1647 | SVN_ERR(editor->close_file(file_baton, NULL, subpool)); |
---|
1648 | /* This should leave the transaction. */ |
---|
1649 | SVN_ERR(editor->abort_edit(edit_baton, subpool)); |
---|
1650 | |
---|
1651 | /* Reopen the transaction. */ |
---|
1652 | SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, subpool)); |
---|
1653 | SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos, |
---|
1654 | txn, "file://test", "/", |
---|
1655 | "plato", "test commit", |
---|
1656 | dummy_commit_cb, |
---|
1657 | NULL, NULL, NULL, |
---|
1658 | subpool)); |
---|
1659 | |
---|
1660 | SVN_ERR(editor->open_root(edit_baton, 1, subpool, &root_baton)); |
---|
1661 | |
---|
1662 | SVN_ERR(editor->add_file("/f2", root_baton, NULL, SVN_INVALID_REVNUM, |
---|
1663 | subpool, &file_baton)); |
---|
1664 | SVN_ERR(editor->close_file(file_baton, NULL, subpool)); |
---|
1665 | |
---|
1666 | /* Finally, commit it. */ |
---|
1667 | SVN_ERR(editor->close_edit(edit_baton, subpool)); |
---|
1668 | |
---|
1669 | /* Check that the edits really happened. */ |
---|
1670 | { |
---|
1671 | static svn_test__tree_entry_t expected_entries[] = { |
---|
1672 | /* path, contents (0 = dir) */ |
---|
1673 | { "iota", "This is the file 'iota'.\n" }, |
---|
1674 | { "A", 0 }, |
---|
1675 | { "A/mu", "This is the file 'mu'.\n" }, |
---|
1676 | { "A/B", 0 }, |
---|
1677 | { "A/B/lambda", "This is the file 'lambda'.\n" }, |
---|
1678 | { "A/B/E", 0 }, |
---|
1679 | { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
---|
1680 | { "A/B/E/beta", "This is the file 'beta'.\n" }, |
---|
1681 | { "A/B/F", 0 }, |
---|
1682 | { "A/C", 0 }, |
---|
1683 | { "A/D", 0 }, |
---|
1684 | { "A/D/gamma", "This is the file 'gamma'.\n" }, |
---|
1685 | { "A/D/G", 0 }, |
---|
1686 | { "A/D/G/pi", "This is the file 'pi'.\n" }, |
---|
1687 | { "A/D/G/rho", "This is the file 'rho'.\n" }, |
---|
1688 | { "A/D/G/tau", "This is the file 'tau'.\n" }, |
---|
1689 | { "A/D/H", 0 }, |
---|
1690 | { "A/D/H/chi", "This is the file 'chi'.\n" }, |
---|
1691 | { "A/D/H/psi", "This is the file 'psi'.\n" }, |
---|
1692 | { "A/D/H/omega", "This is the file 'omega'.\n" }, |
---|
1693 | { "f1", "" }, |
---|
1694 | { "f2", "" } |
---|
1695 | }; |
---|
1696 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, |
---|
1697 | 2, subpool)); |
---|
1698 | SVN_ERR(svn_test__validate_tree |
---|
1699 | (revision_root, expected_entries, |
---|
1700 | sizeof(expected_entries) / sizeof(expected_entries[0]), |
---|
1701 | subpool)); |
---|
1702 | } |
---|
1703 | |
---|
1704 | svn_pool_destroy(subpool); |
---|
1705 | |
---|
1706 | return SVN_NO_ERROR; |
---|
1707 | } |
---|
1708 | |
---|
1709 | |
---|
1710 | struct nls_receiver_baton |
---|
1711 | { |
---|
1712 | int count; |
---|
1713 | svn_location_segment_t *expected_segments; |
---|
1714 | }; |
---|
1715 | |
---|
1716 | |
---|
1717 | static const char * |
---|
1718 | format_segment(svn_location_segment_t *segment, |
---|
1719 | apr_pool_t *pool) |
---|
1720 | { |
---|
1721 | return apr_psprintf(pool, "[r%ld-r%ld: /%s]", |
---|
1722 | segment->range_start, |
---|
1723 | segment->range_end, |
---|
1724 | segment->path ? segment->path : "(null)"); |
---|
1725 | } |
---|
1726 | |
---|
1727 | |
---|
1728 | static svn_error_t * |
---|
1729 | nls_receiver(svn_location_segment_t *segment, |
---|
1730 | void *baton, |
---|
1731 | apr_pool_t *pool) |
---|
1732 | { |
---|
1733 | struct nls_receiver_baton *b = baton; |
---|
1734 | svn_location_segment_t *expected_segment = b->expected_segments + b->count; |
---|
1735 | |
---|
1736 | /* expected_segments->range_end can't be 0, so if we see that, it's |
---|
1737 | our end-of-the-list sentry. */ |
---|
1738 | if (! expected_segment->range_end) |
---|
1739 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
1740 | "Got unexpected location segment: %s", |
---|
1741 | format_segment(segment, pool)); |
---|
1742 | |
---|
1743 | if (expected_segment->range_start != segment->range_start) |
---|
1744 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
1745 | "Location segments differ\n" |
---|
1746 | " Expected location segment: %s\n" |
---|
1747 | " Actual location segment: %s", |
---|
1748 | format_segment(expected_segment, pool), |
---|
1749 | format_segment(segment, pool)); |
---|
1750 | b->count++; |
---|
1751 | return SVN_NO_ERROR; |
---|
1752 | } |
---|
1753 | |
---|
1754 | |
---|
1755 | static svn_error_t * |
---|
1756 | check_location_segments(svn_repos_t *repos, |
---|
1757 | const char *path, |
---|
1758 | svn_revnum_t peg_rev, |
---|
1759 | svn_revnum_t start_rev, |
---|
1760 | svn_revnum_t end_rev, |
---|
1761 | svn_location_segment_t *expected_segments, |
---|
1762 | apr_pool_t *pool) |
---|
1763 | { |
---|
1764 | struct nls_receiver_baton b; |
---|
1765 | svn_location_segment_t *segment; |
---|
1766 | |
---|
1767 | /* Run svn_repos_node_location_segments() with a receiver that |
---|
1768 | validates against EXPECTED_SEGMENTS. */ |
---|
1769 | b.count = 0; |
---|
1770 | b.expected_segments = expected_segments; |
---|
1771 | SVN_ERR(svn_repos_node_location_segments(repos, path, peg_rev, |
---|
1772 | start_rev, end_rev, nls_receiver, |
---|
1773 | &b, NULL, NULL, pool)); |
---|
1774 | |
---|
1775 | /* Make sure we saw all of our expected segments. (If the |
---|
1776 | 'range_end' member of our expected_segments is 0, it's our |
---|
1777 | end-of-the-list sentry. Otherwise, it's some segment we expect |
---|
1778 | to see.) If not, raise an error. */ |
---|
1779 | segment = expected_segments + b.count; |
---|
1780 | if (segment->range_end) |
---|
1781 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
1782 | "Failed to get expected location segment: %s", |
---|
1783 | format_segment(segment, pool)); |
---|
1784 | return SVN_NO_ERROR; |
---|
1785 | } |
---|
1786 | |
---|
1787 | |
---|
1788 | static svn_error_t * |
---|
1789 | node_location_segments(const char **msg, |
---|
1790 | svn_boolean_t msg_only, |
---|
1791 | svn_test_opts_t *opts, |
---|
1792 | apr_pool_t *pool) |
---|
1793 | { |
---|
1794 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1795 | svn_repos_t *repos; |
---|
1796 | svn_fs_t *fs; |
---|
1797 | svn_fs_txn_t *txn; |
---|
1798 | svn_fs_root_t *txn_root, *root; |
---|
1799 | svn_revnum_t youngest_rev = 0; |
---|
1800 | |
---|
1801 | *msg = "test svn_repos_node_location_segments"; |
---|
1802 | if (msg_only) |
---|
1803 | return SVN_NO_ERROR; |
---|
1804 | |
---|
1805 | /* Bail (with success) on known-untestable scenarios */ |
---|
1806 | if ((strcmp(opts->fs_type, "bdb") == 0) |
---|
1807 | && (opts->server_minor_version == 4)) |
---|
1808 | return SVN_NO_ERROR; |
---|
1809 | |
---|
1810 | /* Create the repository. */ |
---|
1811 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-location-segments", |
---|
1812 | opts, pool)); |
---|
1813 | fs = svn_repos_fs(repos); |
---|
1814 | |
---|
1815 | /* Revision 1: Create the Greek tree. */ |
---|
1816 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
1817 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1818 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
1819 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1820 | svn_pool_clear(subpool); |
---|
1821 | |
---|
1822 | /* Revision 2: Modify A/D/H/chi and A/B/E/alpha. */ |
---|
1823 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
1824 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1825 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "2", subpool)); |
---|
1826 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", "2", subpool)); |
---|
1827 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1828 | svn_pool_clear(subpool); |
---|
1829 | |
---|
1830 | /* Revision 3: Copy A/D to A/D2. */ |
---|
1831 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
1832 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1833 | SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool)); |
---|
1834 | SVN_ERR(svn_fs_copy(root, "A/D", txn_root, "A/D2", subpool)); |
---|
1835 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1836 | svn_pool_clear(subpool); |
---|
1837 | |
---|
1838 | /* Revision 4: Modify A/D/H/chi and A/D2/H/chi. */ |
---|
1839 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
1840 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1841 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "4", subpool)); |
---|
1842 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/D2/H/chi", "4", subpool)); |
---|
1843 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1844 | svn_pool_clear(subpool); |
---|
1845 | |
---|
1846 | /* Revision 5: Delete A/D2/G. */ |
---|
1847 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
1848 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1849 | SVN_ERR(svn_fs_delete(txn_root, "A/D2/G", subpool)); |
---|
1850 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1851 | svn_pool_clear(subpool); |
---|
1852 | |
---|
1853 | /* Revision 6: Restore A/D2/G (from version 4). */ |
---|
1854 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
1855 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1856 | SVN_ERR(svn_fs_revision_root(&root, fs, 4, subpool)); |
---|
1857 | SVN_ERR(svn_fs_copy(root, "A/D2/G", txn_root, "A/D2/G", subpool)); |
---|
1858 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1859 | svn_pool_clear(subpool); |
---|
1860 | |
---|
1861 | /* Revision 7: Move A/D2 to A/D (replacing it). */ |
---|
1862 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
1863 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
1864 | SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool)); |
---|
1865 | SVN_ERR(svn_fs_delete(txn_root, "A/D", subpool)); |
---|
1866 | SVN_ERR(svn_fs_copy(root, "A/D2", txn_root, "A/D", subpool)); |
---|
1867 | SVN_ERR(svn_fs_delete(txn_root, "A/D2", subpool)); |
---|
1868 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
1869 | svn_pool_clear(subpool); |
---|
1870 | |
---|
1871 | /* Check locations for /@HEAD. */ |
---|
1872 | { |
---|
1873 | svn_location_segment_t expected_segments[] = |
---|
1874 | { |
---|
1875 | { 0, 7, "" }, |
---|
1876 | { 0 } |
---|
1877 | }; |
---|
1878 | SVN_ERR(check_location_segments(repos, "", |
---|
1879 | SVN_INVALID_REVNUM, |
---|
1880 | SVN_INVALID_REVNUM, |
---|
1881 | SVN_INVALID_REVNUM, |
---|
1882 | expected_segments, pool)); |
---|
1883 | } |
---|
1884 | |
---|
1885 | /* Check locations for A/D@HEAD. */ |
---|
1886 | { |
---|
1887 | svn_location_segment_t expected_segments[] = |
---|
1888 | { |
---|
1889 | { 7, 7, "A/D" }, |
---|
1890 | { 3, 6, "A/D2" }, |
---|
1891 | { 1, 2, "A/D" }, |
---|
1892 | { 0 } |
---|
1893 | }; |
---|
1894 | SVN_ERR(check_location_segments(repos, "A/D", |
---|
1895 | SVN_INVALID_REVNUM, |
---|
1896 | SVN_INVALID_REVNUM, |
---|
1897 | SVN_INVALID_REVNUM, |
---|
1898 | expected_segments, pool)); |
---|
1899 | } |
---|
1900 | |
---|
1901 | /* Check a subset of the locations for A/D@HEAD. */ |
---|
1902 | { |
---|
1903 | svn_location_segment_t expected_segments[] = |
---|
1904 | { |
---|
1905 | { 3, 5, "A/D2" }, |
---|
1906 | { 2, 2, "A/D" }, |
---|
1907 | { 0 } |
---|
1908 | }; |
---|
1909 | SVN_ERR(check_location_segments(repos, "A/D", |
---|
1910 | SVN_INVALID_REVNUM, |
---|
1911 | 5, |
---|
1912 | 2, |
---|
1913 | expected_segments, pool)); |
---|
1914 | } |
---|
1915 | |
---|
1916 | /* Check a subset of locations for A/D2@5. */ |
---|
1917 | { |
---|
1918 | svn_location_segment_t expected_segments[] = |
---|
1919 | { |
---|
1920 | { 3, 3, "A/D2" }, |
---|
1921 | { 2, 2, "A/D" }, |
---|
1922 | { 0 } |
---|
1923 | }; |
---|
1924 | SVN_ERR(check_location_segments(repos, "A/D2", |
---|
1925 | 5, |
---|
1926 | 3, |
---|
1927 | 2, |
---|
1928 | expected_segments, pool)); |
---|
1929 | } |
---|
1930 | |
---|
1931 | /* Check locations for A/D@6. */ |
---|
1932 | { |
---|
1933 | svn_location_segment_t expected_segments[] = |
---|
1934 | { |
---|
1935 | { 1, 6, "A/D" }, |
---|
1936 | { 0 } |
---|
1937 | }; |
---|
1938 | SVN_ERR(check_location_segments(repos, "A/D", |
---|
1939 | 6, |
---|
1940 | 6, |
---|
1941 | SVN_INVALID_REVNUM, |
---|
1942 | expected_segments, pool)); |
---|
1943 | } |
---|
1944 | |
---|
1945 | /* Check locations for A/D/G@HEAD. */ |
---|
1946 | { |
---|
1947 | svn_location_segment_t expected_segments[] = |
---|
1948 | { |
---|
1949 | { 7, 7, "A/D/G" }, |
---|
1950 | { 6, 6, "A/D2/G" }, |
---|
1951 | { 5, 5, NULL }, |
---|
1952 | { 3, 4, "A/D2/G" }, |
---|
1953 | { 1, 2, "A/D2/G" }, |
---|
1954 | { 0 } |
---|
1955 | }; |
---|
1956 | SVN_ERR(check_location_segments(repos, "A/D/G", |
---|
1957 | SVN_INVALID_REVNUM, |
---|
1958 | SVN_INVALID_REVNUM, |
---|
1959 | SVN_INVALID_REVNUM, |
---|
1960 | expected_segments, pool)); |
---|
1961 | } |
---|
1962 | |
---|
1963 | /* Check a subset of the locations for A/D/G@HEAD. */ |
---|
1964 | { |
---|
1965 | svn_location_segment_t expected_segments[] = |
---|
1966 | { |
---|
1967 | { 3, 3, "A/D2/G" }, |
---|
1968 | { 2, 2, "A/D2/G" }, |
---|
1969 | { 0 } |
---|
1970 | }; |
---|
1971 | SVN_ERR(check_location_segments(repos, "A/D/G", |
---|
1972 | SVN_INVALID_REVNUM, |
---|
1973 | 3, |
---|
1974 | 2, |
---|
1975 | expected_segments, pool)); |
---|
1976 | } |
---|
1977 | |
---|
1978 | return SVN_NO_ERROR; |
---|
1979 | } |
---|
1980 | |
---|
1981 | |
---|
1982 | /* Test that the reporter doesn't send deltas under excluded paths. */ |
---|
1983 | static svn_error_t * |
---|
1984 | reporter_depth_exclude(const char **msg, |
---|
1985 | svn_boolean_t msg_only, |
---|
1986 | svn_test_opts_t *opts, |
---|
1987 | apr_pool_t *pool) |
---|
1988 | { |
---|
1989 | svn_repos_t *repos; |
---|
1990 | svn_fs_t *fs; |
---|
1991 | svn_fs_txn_t *txn; |
---|
1992 | svn_fs_root_t *txn_root; |
---|
1993 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
1994 | svn_revnum_t youngest_rev; |
---|
1995 | const svn_delta_editor_t *editor; |
---|
1996 | void *edit_baton, *report_baton; |
---|
1997 | svn_error_t *err; |
---|
1998 | |
---|
1999 | *msg = "test reporter and svn_depth_exclude"; |
---|
2000 | |
---|
2001 | if (msg_only) |
---|
2002 | return SVN_NO_ERROR; |
---|
2003 | |
---|
2004 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-reporter-depth-exclude", |
---|
2005 | opts, pool)); |
---|
2006 | fs = svn_repos_fs(repos); |
---|
2007 | |
---|
2008 | /* Prepare a txn to receive the greek tree. */ |
---|
2009 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); |
---|
2010 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
2011 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
2012 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
2013 | svn_pool_clear(subpool); |
---|
2014 | |
---|
2015 | /* Revision 2: make a bunch of changes */ |
---|
2016 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
2017 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
2018 | { |
---|
2019 | static svn_test__txn_script_command_t script_entries[] = { |
---|
2020 | { 'e', "iota", "Changed file 'iota'.\n" }, |
---|
2021 | { 'e', "A/D/G/pi", "Changed file 'pi'.\n" }, |
---|
2022 | { 'e', "A/mu", "Changed file 'mu'.\n" }, |
---|
2023 | { 'a', "A/D/foo", "New file 'foo'.\n" }, |
---|
2024 | { 'a', "A/B/bar", "New file 'bar'.\n" }, |
---|
2025 | { 'd', "A/D/H", NULL }, |
---|
2026 | { 'd', "A/B/E/beta", NULL } |
---|
2027 | }; |
---|
2028 | SVN_ERR(svn_test__txn_script_exec(txn_root, |
---|
2029 | script_entries, |
---|
2030 | sizeof(script_entries)/ |
---|
2031 | sizeof(script_entries[0]), |
---|
2032 | subpool)); |
---|
2033 | } |
---|
2034 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
2035 | svn_pool_clear(subpool); |
---|
2036 | |
---|
2037 | /* Confirm the contents of r2. */ |
---|
2038 | { |
---|
2039 | svn_fs_root_t *revision_root; |
---|
2040 | static svn_test__tree_entry_t entries[] = { |
---|
2041 | { "iota", "Changed file 'iota'.\n" }, |
---|
2042 | { "A", 0 }, |
---|
2043 | { "A/mu", "Changed file 'mu'.\n" }, |
---|
2044 | { "A/B", 0 }, |
---|
2045 | { "A/B/bar", "New file 'bar'.\n" }, |
---|
2046 | { "A/B/lambda", "This is the file 'lambda'.\n" }, |
---|
2047 | { "A/B/E", 0 }, |
---|
2048 | { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
---|
2049 | { "A/B/F", 0 }, |
---|
2050 | { "A/C", 0 }, |
---|
2051 | { "A/D", 0 }, |
---|
2052 | { "A/D/foo", "New file 'foo'.\n" }, |
---|
2053 | { "A/D/gamma", "This is the file 'gamma'.\n" }, |
---|
2054 | { "A/D/G", 0 }, |
---|
2055 | { "A/D/G/pi", "Changed file 'pi'.\n" }, |
---|
2056 | { "A/D/G/rho", "This is the file 'rho'.\n" }, |
---|
2057 | { "A/D/G/tau", "This is the file 'tau'.\n" }, |
---|
2058 | }; |
---|
2059 | SVN_ERR(svn_fs_revision_root(&revision_root, fs, |
---|
2060 | youngest_rev, subpool)); |
---|
2061 | SVN_ERR(svn_test__validate_tree(revision_root, |
---|
2062 | entries, |
---|
2063 | sizeof(entries)/sizeof(entries[0]), |
---|
2064 | subpool)); |
---|
2065 | } |
---|
2066 | |
---|
2067 | /* Run an update from r1 to r2, excluding iota and everything under |
---|
2068 | A/D. Record the editor commands in a temporary txn. */ |
---|
2069 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 1, subpool)); |
---|
2070 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
2071 | SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs, |
---|
2072 | txn_root, "", subpool)); |
---|
2073 | |
---|
2074 | SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL, |
---|
2075 | TRUE, svn_depth_infinity, FALSE, FALSE, |
---|
2076 | editor, edit_baton, NULL, NULL, subpool)); |
---|
2077 | SVN_ERR(svn_repos_set_path3(report_baton, "", 1, |
---|
2078 | svn_depth_infinity, |
---|
2079 | FALSE, NULL, subpool)); |
---|
2080 | SVN_ERR(svn_repos_set_path3(report_baton, "iota", SVN_INVALID_REVNUM, |
---|
2081 | svn_depth_exclude, |
---|
2082 | FALSE, NULL, subpool)); |
---|
2083 | SVN_ERR(svn_repos_set_path3(report_baton, "A/D", SVN_INVALID_REVNUM, |
---|
2084 | svn_depth_exclude, |
---|
2085 | FALSE, NULL, subpool)); |
---|
2086 | SVN_ERR(svn_repos_finish_report(report_baton, subpool)); |
---|
2087 | |
---|
2088 | /* Confirm the contents of the txn. */ |
---|
2089 | /* This should have iota and A/D from r1, and everything else from |
---|
2090 | r2. */ |
---|
2091 | { |
---|
2092 | static svn_test__tree_entry_t entries[] = { |
---|
2093 | { "iota", "This is the file 'iota'.\n" }, |
---|
2094 | { "A", 0 }, |
---|
2095 | { "A/mu", "Changed file 'mu'.\n" }, |
---|
2096 | { "A/B", 0 }, |
---|
2097 | { "A/B/bar", "New file 'bar'.\n" }, |
---|
2098 | { "A/B/lambda", "This is the file 'lambda'.\n" }, |
---|
2099 | { "A/B/E", 0 }, |
---|
2100 | { "A/B/E/alpha", "This is the file 'alpha'.\n" }, |
---|
2101 | { "A/B/F", 0 }, |
---|
2102 | { "A/C", 0 }, |
---|
2103 | { "A/D", 0 }, |
---|
2104 | { "A/D/gamma", "This is the file 'gamma'.\n" }, |
---|
2105 | { "A/D/G", 0 }, |
---|
2106 | { "A/D/G/pi", "This is the file 'pi'.\n" }, |
---|
2107 | { "A/D/G/rho", "This is the file 'rho'.\n" }, |
---|
2108 | { "A/D/G/tau", "This is the file 'tau'.\n" }, |
---|
2109 | { "A/D/H", 0 }, |
---|
2110 | { "A/D/H/chi", "This is the file 'chi'.\n" }, |
---|
2111 | { "A/D/H/psi", "This is the file 'psi'.\n" }, |
---|
2112 | { "A/D/H/omega", "This is the file 'omega'.\n" } |
---|
2113 | }; |
---|
2114 | SVN_ERR(svn_test__validate_tree(txn_root, |
---|
2115 | entries, |
---|
2116 | sizeof(entries)/sizeof(entries[0]), |
---|
2117 | subpool)); |
---|
2118 | } |
---|
2119 | |
---|
2120 | /* Clean up after ourselves. */ |
---|
2121 | svn_error_clear(svn_fs_abort_txn(txn, subpool)); |
---|
2122 | svn_pool_clear(subpool); |
---|
2123 | |
---|
2124 | /* Expect an error on an illegal report for r1 to r2. The illegal |
---|
2125 | sequence is that we exclude A/D, then set_path() below A/D. */ |
---|
2126 | SVN_ERR(svn_fs_begin_txn(&txn, fs, 1, subpool)); |
---|
2127 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
2128 | SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs, |
---|
2129 | txn_root, "", subpool)); |
---|
2130 | |
---|
2131 | SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL, |
---|
2132 | TRUE, svn_depth_infinity, FALSE, FALSE, |
---|
2133 | editor, edit_baton, NULL, NULL, subpool)); |
---|
2134 | SVN_ERR(svn_repos_set_path3(report_baton, "", 1, |
---|
2135 | svn_depth_infinity, |
---|
2136 | FALSE, NULL, subpool)); |
---|
2137 | SVN_ERR(svn_repos_set_path3(report_baton, "iota", SVN_INVALID_REVNUM, |
---|
2138 | svn_depth_exclude, |
---|
2139 | FALSE, NULL, subpool)); |
---|
2140 | SVN_ERR(svn_repos_set_path3(report_baton, "A/D", SVN_INVALID_REVNUM, |
---|
2141 | svn_depth_exclude, |
---|
2142 | FALSE, NULL, subpool)); |
---|
2143 | |
---|
2144 | /* This is the illegal call, since A/D was excluded above; the call |
---|
2145 | itself will not error, but finish_report() will. As of r28098, |
---|
2146 | this delayed error behavior is not actually promised by the |
---|
2147 | reporter API, which merely warns callers not to touch a path |
---|
2148 | underneath a previously excluded path without defining what will |
---|
2149 | happen if they do. However, it's still useful to test for the |
---|
2150 | error, since the reporter code is sensitive and we'd certainly |
---|
2151 | want to know about it if the behavior were to change. */ |
---|
2152 | SVN_ERR(svn_repos_set_path3(report_baton, "A/D/G/pi", |
---|
2153 | SVN_INVALID_REVNUM, |
---|
2154 | svn_depth_infinity, |
---|
2155 | FALSE, NULL, subpool)); |
---|
2156 | err = svn_repos_finish_report(report_baton, subpool); |
---|
2157 | if (! err) |
---|
2158 | { |
---|
2159 | return svn_error_createf |
---|
2160 | (SVN_ERR_TEST_FAILED, NULL, |
---|
2161 | "Illegal report of \"A/D/G/pi\" did not error as expected"); |
---|
2162 | } |
---|
2163 | else if (err->apr_err != SVN_ERR_FS_NOT_FOUND) |
---|
2164 | { |
---|
2165 | return svn_error_createf |
---|
2166 | (SVN_ERR_TEST_FAILED, err, |
---|
2167 | "Illegal report of \"A/D/G/pi\" got wrong kind of error:"); |
---|
2168 | } |
---|
2169 | |
---|
2170 | /* Clean up after ourselves. */ |
---|
2171 | svn_error_clear(err); |
---|
2172 | svn_error_clear(svn_fs_abort_txn(txn, subpool)); |
---|
2173 | |
---|
2174 | svn_pool_destroy(subpool); |
---|
2175 | |
---|
2176 | return SVN_NO_ERROR; |
---|
2177 | } |
---|
2178 | |
---|
2179 | |
---|
2180 | |
---|
2181 | /* Test if prop values received by the server are validated. |
---|
2182 | * These tests "send" property values to the server and diagnose the |
---|
2183 | * behaviour. |
---|
2184 | */ |
---|
2185 | |
---|
2186 | /* Helper function that makes an arbitrary change to a given repository |
---|
2187 | * REPOS and runs a commit with a specific revision property set to a |
---|
2188 | * certain value. The property name, type and value are given in PROP_KEY, |
---|
2189 | * PROP_KLEN and PROP_VAL, as in apr_hash_set(), using a const char* key. |
---|
2190 | * |
---|
2191 | * The FILENAME argument names a file in the test repository to add in |
---|
2192 | * this commit, e.g. "/A/should_fail_1". |
---|
2193 | * |
---|
2194 | * On success, the given file is added to the repository. So, using |
---|
2195 | * the same name multiple times on the same repository might fail. Thus, |
---|
2196 | * use different FILENAME arguments for every call to this function |
---|
2197 | * (e.g. "/A/f1", "/A/f2", "/A/f3" etc). |
---|
2198 | */ |
---|
2199 | static svn_error_t * |
---|
2200 | prop_validation_commit_with_revprop(const char *filename, |
---|
2201 | const char *prop_key, |
---|
2202 | apr_ssize_t prop_klen, |
---|
2203 | const svn_string_t *prop_val, |
---|
2204 | svn_repos_t *repos, |
---|
2205 | apr_pool_t *pool) |
---|
2206 | { |
---|
2207 | const svn_delta_editor_t *editor; |
---|
2208 | void *edit_baton; |
---|
2209 | void *root_baton; |
---|
2210 | void *file_baton; |
---|
2211 | |
---|
2212 | /* Prepare revision properties */ |
---|
2213 | apr_hash_t *revprop_table = apr_hash_make(pool); |
---|
2214 | |
---|
2215 | /* Add the requested property */ |
---|
2216 | apr_hash_set(revprop_table, prop_key, prop_klen, prop_val); |
---|
2217 | |
---|
2218 | /* Set usual author and log props, if not set already */ |
---|
2219 | if (strcmp(prop_key, SVN_PROP_REVISION_AUTHOR) != 0) |
---|
2220 | { |
---|
2221 | apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, |
---|
2222 | APR_HASH_KEY_STRING, |
---|
2223 | svn_string_create("plato", pool)); |
---|
2224 | } |
---|
2225 | else |
---|
2226 | if (strcmp(prop_key, SVN_PROP_REVISION_LOG) != 0) |
---|
2227 | { |
---|
2228 | apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, |
---|
2229 | APR_HASH_KEY_STRING, |
---|
2230 | svn_string_create("revision log", pool)); |
---|
2231 | } |
---|
2232 | |
---|
2233 | /* Make an arbitrary change and commit using above values... */ |
---|
2234 | |
---|
2235 | SVN_ERR(svn_repos_get_commit_editor5(&editor, &edit_baton, repos, |
---|
2236 | NULL, "file://test", "/", |
---|
2237 | revprop_table, |
---|
2238 | NULL, NULL, NULL, NULL, pool)); |
---|
2239 | |
---|
2240 | SVN_ERR(editor->open_root(edit_baton, 0, pool, &root_baton)); |
---|
2241 | |
---|
2242 | SVN_ERR(editor->add_file(filename, root_baton, NULL, |
---|
2243 | SVN_INVALID_REVNUM, pool, |
---|
2244 | &file_baton)); |
---|
2245 | |
---|
2246 | SVN_ERR(editor->close_file(file_baton, NULL, pool)); |
---|
2247 | |
---|
2248 | SVN_ERR(editor->close_directory(root_baton, pool)); |
---|
2249 | |
---|
2250 | SVN_ERR(editor->close_edit(edit_baton, pool)); |
---|
2251 | |
---|
2252 | return SVN_NO_ERROR; |
---|
2253 | } |
---|
2254 | |
---|
2255 | |
---|
2256 | /* Expect failure of invalid commit in these cases: |
---|
2257 | * - log message contains invalid UTF-8 octet (issue 1796) |
---|
2258 | * - log message contains invalid linefeed style (non-LF) (issue 1796) |
---|
2259 | */ |
---|
2260 | static svn_error_t * |
---|
2261 | prop_validation(const char **msg, |
---|
2262 | svn_boolean_t msg_only, |
---|
2263 | svn_test_opts_t *opts, |
---|
2264 | apr_pool_t *pool) |
---|
2265 | { |
---|
2266 | svn_error_t *err; |
---|
2267 | svn_repos_t *repos; |
---|
2268 | const char non_utf8_string[5] = { 'a', 0xff, 'b', '\n', 0 }; |
---|
2269 | const char *non_lf_string = "a\r\nb\n\rc\rd\n"; |
---|
2270 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
2271 | |
---|
2272 | *msg = "test if revprops are validated by repos"; |
---|
2273 | |
---|
2274 | if (msg_only) |
---|
2275 | return SVN_NO_ERROR; |
---|
2276 | |
---|
2277 | /* Create a filesystem and repository. */ |
---|
2278 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-prop-validation", |
---|
2279 | opts, subpool)); |
---|
2280 | |
---|
2281 | |
---|
2282 | /* Test an invalid commit log message: UTF-8 */ |
---|
2283 | err = prop_validation_commit_with_revprop |
---|
2284 | ("/non_utf8_log_msg", |
---|
2285 | SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING, |
---|
2286 | svn_string_create(non_utf8_string, subpool), |
---|
2287 | repos, subpool); |
---|
2288 | |
---|
2289 | if (err == SVN_NO_ERROR) |
---|
2290 | return svn_error_create(SVN_ERR_TEST_FAILED, err, |
---|
2291 | "Failed to reject a log with invalid " |
---|
2292 | "UTF-8"); |
---|
2293 | else |
---|
2294 | if (err->apr_err != SVN_ERR_BAD_PROPERTY_VALUE) |
---|
2295 | return svn_error_create(SVN_ERR_TEST_FAILED, err, |
---|
2296 | "Expected SVN_ERR_BAD_PROPERTY_VALUE for " |
---|
2297 | "a log with invalid UTF-8, " |
---|
2298 | "got another error."); |
---|
2299 | svn_error_clear(err); |
---|
2300 | |
---|
2301 | |
---|
2302 | /* Test an invalid commit log message: LF */ |
---|
2303 | err = prop_validation_commit_with_revprop |
---|
2304 | ("/non_lf_log_msg", |
---|
2305 | SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING, |
---|
2306 | svn_string_create(non_lf_string, subpool), |
---|
2307 | repos, subpool); |
---|
2308 | |
---|
2309 | if (err == SVN_NO_ERROR) |
---|
2310 | return svn_error_create(SVN_ERR_TEST_FAILED, err, |
---|
2311 | "Failed to reject a log with inconsistent " |
---|
2312 | "line ending style"); |
---|
2313 | else |
---|
2314 | if (err->apr_err != SVN_ERR_BAD_PROPERTY_VALUE) |
---|
2315 | return svn_error_create(SVN_ERR_TEST_FAILED, err, |
---|
2316 | "Expected SVN_ERR_BAD_PROPERTY_VALUE for " |
---|
2317 | "a log with inconsistent line ending style, " |
---|
2318 | "got another error."); |
---|
2319 | svn_error_clear(err); |
---|
2320 | |
---|
2321 | |
---|
2322 | /* Done. */ |
---|
2323 | svn_pool_destroy(subpool); |
---|
2324 | |
---|
2325 | return SVN_NO_ERROR; |
---|
2326 | } |
---|
2327 | |
---|
2328 | |
---|
2329 | |
---|
2330 | /* Tests for svn_repos_get_logsN() */ |
---|
2331 | |
---|
2332 | /* Log receiver which simple increments a counter. */ |
---|
2333 | static svn_error_t * |
---|
2334 | log_receiver(void *baton, |
---|
2335 | svn_log_entry_t *log_entry, |
---|
2336 | apr_pool_t *pool) |
---|
2337 | { |
---|
2338 | int *count = baton; |
---|
2339 | (*count)++; |
---|
2340 | return SVN_NO_ERROR; |
---|
2341 | } |
---|
2342 | |
---|
2343 | |
---|
2344 | static svn_error_t * |
---|
2345 | get_logs(const char **msg, |
---|
2346 | svn_boolean_t msg_only, |
---|
2347 | svn_test_opts_t *opts, |
---|
2348 | apr_pool_t *pool) |
---|
2349 | { |
---|
2350 | svn_repos_t *repos; |
---|
2351 | svn_fs_t *fs; |
---|
2352 | svn_fs_txn_t *txn; |
---|
2353 | svn_fs_root_t *txn_root; |
---|
2354 | svn_revnum_t start, end, youngest_rev = 0; |
---|
2355 | apr_pool_t *subpool = svn_pool_create(pool); |
---|
2356 | |
---|
2357 | *msg = "test svn_repos_get_logs ranges and limits"; |
---|
2358 | |
---|
2359 | if (msg_only) |
---|
2360 | return SVN_NO_ERROR; |
---|
2361 | |
---|
2362 | /* Create a filesystem and repository. */ |
---|
2363 | SVN_ERR(svn_test__create_repos(&repos, "test-repo-get-logs", |
---|
2364 | opts, pool)); |
---|
2365 | fs = svn_repos_fs(repos); |
---|
2366 | |
---|
2367 | /* Revision 1: Add the Greek tree. */ |
---|
2368 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
2369 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
2370 | SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); |
---|
2371 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
2372 | |
---|
2373 | /* Revision 2: Tweak A/mu and A/B/E/alpha. */ |
---|
2374 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
2375 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
2376 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/mu", |
---|
2377 | "Revision 2", subpool)); |
---|
2378 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", |
---|
2379 | "Revision 2", subpool)); |
---|
2380 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
2381 | |
---|
2382 | /* Revision 3: Tweak A/B/E/alpha and A/B/E/beta. */ |
---|
2383 | SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
---|
2384 | SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
---|
2385 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", |
---|
2386 | "Revision 3", subpool)); |
---|
2387 | SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/beta", |
---|
2388 | "Revision 3", subpool)); |
---|
2389 | SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); |
---|
2390 | |
---|
2391 | |
---|
2392 | for (start = 0; start <= youngest_rev; start++) |
---|
2393 | { |
---|
2394 | for (end = 0; end <= youngest_rev; end++) |
---|
2395 | { |
---|
2396 | svn_revnum_t start_arg = start ? start : SVN_INVALID_REVNUM; |
---|
2397 | svn_revnum_t end_arg = end ? end : SVN_INVALID_REVNUM; |
---|
2398 | svn_revnum_t eff_start = start ? start : youngest_rev; |
---|
2399 | svn_revnum_t eff_end = end ? end : youngest_rev; |
---|
2400 | int limit, max_logs = |
---|
2401 | MAX(eff_start, eff_end) + 1 - MIN(eff_start, eff_end); |
---|
2402 | int num_logs; |
---|
2403 | |
---|
2404 | for (limit = 0; limit <= max_logs; limit++) |
---|
2405 | { |
---|
2406 | int num_expected = limit ? limit : max_logs; |
---|
2407 | |
---|
2408 | svn_pool_clear(subpool); |
---|
2409 | num_logs = 0; |
---|
2410 | SVN_ERR(svn_repos_get_logs4(repos, NULL, start_arg, end_arg, |
---|
2411 | limit, FALSE, FALSE, FALSE, NULL, |
---|
2412 | NULL, NULL, log_receiver, &num_logs, |
---|
2413 | subpool)); |
---|
2414 | if (num_logs != num_expected) |
---|
2415 | return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
---|
2416 | "Log with start=%ld,end=%ld,limit=%d " |
---|
2417 | "returned %d entries (expected %d)", |
---|
2418 | start_arg, end_arg, limit, |
---|
2419 | num_logs, max_logs); |
---|
2420 | } |
---|
2421 | } |
---|
2422 | } |
---|
2423 | svn_pool_destroy(subpool); |
---|
2424 | return SVN_NO_ERROR; |
---|
2425 | } |
---|
2426 | |
---|
2427 | |
---|
2428 | |
---|
2429 | /* The test table. */ |
---|
2430 | |
---|
2431 | struct svn_test_descriptor_t test_funcs[] = |
---|
2432 | { |
---|
2433 | SVN_TEST_NULL, |
---|
2434 | SVN_TEST_PASS(dir_deltas), |
---|
2435 | SVN_TEST_PASS(node_tree_delete_under_copy), |
---|
2436 | SVN_TEST_PASS(revisions_changed), |
---|
2437 | SVN_TEST_PASS(node_locations), |
---|
2438 | SVN_TEST_PASS(node_locations2), |
---|
2439 | SVN_TEST_PASS(rmlocks), |
---|
2440 | SVN_TEST_PASS(authz), |
---|
2441 | SVN_TEST_PASS(commit_editor_authz), |
---|
2442 | SVN_TEST_PASS(commit_continue_txn), |
---|
2443 | SVN_TEST_PASS(node_location_segments), |
---|
2444 | SVN_TEST_PASS(reporter_depth_exclude), |
---|
2445 | SVN_TEST_PASS(prop_validation), |
---|
2446 | SVN_TEST_PASS(get_logs), |
---|
2447 | SVN_TEST_NULL |
---|
2448 | }; |
---|