In the command-line client, forbid
[svn.git] / subversion / tests / libsvn_repos / repos-test.c
blob6bbd7e35e34cc36b4563fe7d69e5e9a0965b72e6
1 /* repos-test.c --- tests for the filesystem
3 * ====================================================================
4 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
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.
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 * ====================================================================
18 #include <stdlib.h>
19 #include <string.h>
20 #include <apr_pools.h>
21 #include <apr_md5.h>
22 #include "svn_pools.h"
23 #include "svn_error.h"
24 #include "svn_fs.h"
25 #include "svn_repos.h"
26 #include "svn_path.h"
27 #include "svn_delta.h"
28 #include "svn_config.h"
30 #include "../svn_test.h"
31 #include "../svn_test_fs.h"
33 #include "dir-delta-editor.h"
35 /* Used to terminate lines in large multi-line string literals. */
36 #define NL APR_EOL_STR
40 static svn_error_t *
41 dir_deltas(const char **msg,
42 svn_boolean_t msg_only,
43 svn_test_opts_t *opts,
44 apr_pool_t *pool)
46 svn_repos_t *repos;
47 svn_fs_t *fs;
48 svn_fs_txn_t *txn;
49 svn_fs_root_t *txn_root, *revision_root;
50 svn_revnum_t youngest_rev;
51 void *edit_baton;
52 const svn_delta_editor_t *editor;
53 svn_test__tree_t expected_trees[8];
54 int revision_count = 0;
55 int i, j;
56 apr_pool_t *subpool = svn_pool_create(pool);
58 *msg = "test svn_repos_dir_delta2";
60 if (msg_only)
61 return SVN_NO_ERROR;
63 /* The Test Plan
65 The filesystem function svn_repos_dir_delta2 exists to drive an
66 editor in such a way that given a source tree S and a target tree
67 T, that editor manipulation will transform S into T, insomuch as
68 directories and files, and their contents and properties, go.
69 The general notion of the test plan will be to create pairs of
70 trees (S, T), and an editor that edits a copy of tree S, run them
71 through svn_repos_dir_delta2, and then verify that the edited copy of
72 S is identical to T when it is all said and done. */
74 /* Create a filesystem and repository. */
75 SVN_ERR(svn_test__create_repos(&repos, "test-repo-dir-deltas",
76 opts->fs_type, pool));
77 fs = svn_repos_fs(repos);
78 expected_trees[revision_count].num_entries = 0;
79 expected_trees[revision_count++].entries = 0;
81 /* Prepare a txn to receive the greek tree. */
82 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
83 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
84 SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
85 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
87 /***********************************************************************/
88 /* REVISION 1 */
89 /***********************************************************************/
91 static svn_test__tree_entry_t expected_entries[] = {
92 /* path, contents (0 = dir) */
93 { "iota", "This is the file 'iota'.\n" },
94 { "A", 0 },
95 { "A/mu", "This is the file 'mu'.\n" },
96 { "A/B", 0 },
97 { "A/B/lambda", "This is the file 'lambda'.\n" },
98 { "A/B/E", 0 },
99 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
100 { "A/B/E/beta", "This is the file 'beta'.\n" },
101 { "A/B/F", 0 },
102 { "A/C", 0 },
103 { "A/D", 0 },
104 { "A/D/gamma", "This is the file 'gamma'.\n" },
105 { "A/D/G", 0 },
106 { "A/D/G/pi", "This is the file 'pi'.\n" },
107 { "A/D/G/rho", "This is the file 'rho'.\n" },
108 { "A/D/G/tau", "This is the file 'tau'.\n" },
109 { "A/D/H", 0 },
110 { "A/D/H/chi", "This is the file 'chi'.\n" },
111 { "A/D/H/psi", "This is the file 'psi'.\n" },
112 { "A/D/H/omega", "This is the file 'omega'.\n" }
114 expected_trees[revision_count].entries = expected_entries;
115 expected_trees[revision_count].num_entries = 20;
116 SVN_ERR(svn_fs_revision_root(&revision_root, fs,
117 youngest_rev, subpool));
118 SVN_ERR(svn_test__validate_tree
119 (revision_root, expected_trees[revision_count].entries,
120 expected_trees[revision_count].num_entries, subpool));
121 revision_count++;
123 svn_pool_clear(subpool);
125 /* Make a new txn based on the youngest revision, make some changes,
126 and commit those changes (which makes a new youngest
127 revision). */
128 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
129 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
131 static svn_test__txn_script_command_t script_entries[] = {
132 { 'a', "A/delta", "This is the file 'delta'.\n" },
133 { 'a', "A/epsilon", "This is the file 'epsilon'.\n" },
134 { 'a', "A/B/Z", 0 },
135 { 'a', "A/B/Z/zeta", "This is the file 'zeta'.\n" },
136 { 'd', "A/C", 0 },
137 { 'd', "A/mu", "" },
138 { 'd', "A/D/G/tau", "" },
139 { 'd', "A/D/H/omega", "" },
140 { 'e', "iota", "Changed file 'iota'.\n" },
141 { 'e', "A/D/G/rho", "Changed file 'rho'.\n" }
143 SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 10,
144 subpool));
146 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
148 /***********************************************************************/
149 /* REVISION 2 */
150 /***********************************************************************/
152 static svn_test__tree_entry_t expected_entries[] = {
153 /* path, contents (0 = dir) */
154 { "iota", "Changed file 'iota'.\n" },
155 { "A", 0 },
156 { "A/delta", "This is the file 'delta'.\n" },
157 { "A/epsilon", "This is the file 'epsilon'.\n" },
158 { "A/B", 0 },
159 { "A/B/lambda", "This is the file 'lambda'.\n" },
160 { "A/B/E", 0 },
161 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
162 { "A/B/E/beta", "This is the file 'beta'.\n" },
163 { "A/B/F", 0 },
164 { "A/B/Z", 0 },
165 { "A/B/Z/zeta", "This is the file 'zeta'.\n" },
166 { "A/D", 0 },
167 { "A/D/gamma", "This is the file 'gamma'.\n" },
168 { "A/D/G", 0 },
169 { "A/D/G/pi", "This is the file 'pi'.\n" },
170 { "A/D/G/rho", "Changed file 'rho'.\n" },
171 { "A/D/H", 0 },
172 { "A/D/H/chi", "This is the file 'chi'.\n" },
173 { "A/D/H/psi", "This is the file 'psi'.\n" }
175 expected_trees[revision_count].entries = expected_entries;
176 expected_trees[revision_count].num_entries = 20;
177 SVN_ERR(svn_fs_revision_root(&revision_root, fs,
178 youngest_rev, subpool));
179 SVN_ERR(svn_test__validate_tree
180 (revision_root, expected_trees[revision_count].entries,
181 expected_trees[revision_count].num_entries, subpool));
182 revision_count++;
184 svn_pool_clear(subpool);
186 /* Make a new txn based on the youngest revision, make some changes,
187 and commit those changes (which makes a new youngest
188 revision). */
189 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
190 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
192 static svn_test__txn_script_command_t script_entries[] = {
193 { 'a', "A/mu", "Re-added file 'mu'.\n" },
194 { 'a', "A/D/H/omega", 0 }, /* re-add omega as directory! */
195 { 'd', "iota", "" },
196 { 'e', "A/delta", "This is the file 'delta'.\nLine 2.\n" }
198 SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 4, subpool));
200 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
202 /***********************************************************************/
203 /* REVISION 3 */
204 /***********************************************************************/
206 static svn_test__tree_entry_t expected_entries[] = {
207 /* path, contents (0 = dir) */
208 { "A", 0 },
209 { "A/delta", "This is the file 'delta'.\nLine 2.\n" },
210 { "A/epsilon", "This is the file 'epsilon'.\n" },
211 { "A/mu", "Re-added file 'mu'.\n" },
212 { "A/B", 0 },
213 { "A/B/lambda", "This is the file 'lambda'.\n" },
214 { "A/B/E", 0 },
215 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
216 { "A/B/E/beta", "This is the file 'beta'.\n" },
217 { "A/B/F", 0 },
218 { "A/B/Z", 0 },
219 { "A/B/Z/zeta", "This is the file 'zeta'.\n" },
220 { "A/D", 0 },
221 { "A/D/gamma", "This is the file 'gamma'.\n" },
222 { "A/D/G", 0 },
223 { "A/D/G/pi", "This is the file 'pi'.\n" },
224 { "A/D/G/rho", "Changed file 'rho'.\n" },
225 { "A/D/H", 0 },
226 { "A/D/H/chi", "This is the file 'chi'.\n" },
227 { "A/D/H/psi", "This is the file 'psi'.\n" },
228 { "A/D/H/omega", 0 }
230 expected_trees[revision_count].entries = expected_entries;
231 expected_trees[revision_count].num_entries = 21;
232 SVN_ERR(svn_fs_revision_root(&revision_root, fs,
233 youngest_rev, subpool));
234 SVN_ERR(svn_test__validate_tree
235 (revision_root, expected_trees[revision_count].entries,
236 expected_trees[revision_count].num_entries, subpool));
237 revision_count++;
239 svn_pool_clear(subpool);
241 /* Make a new txn based on the youngest revision, make some changes,
242 and commit those changes (which makes a new youngest
243 revision). */
244 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
245 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
246 SVN_ERR(svn_fs_revision_root(&revision_root, fs, youngest_rev, subpool));
247 SVN_ERR(svn_fs_copy(revision_root, "A/D/G",
248 txn_root, "A/D/G2",
249 subpool));
250 SVN_ERR(svn_fs_copy(revision_root, "A/epsilon",
251 txn_root, "A/B/epsilon",
252 subpool));
253 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
255 /***********************************************************************/
256 /* REVISION 4 */
257 /***********************************************************************/
259 static svn_test__tree_entry_t expected_entries[] = {
260 /* path, contents (0 = dir) */
261 { "A", 0 },
262 { "A/delta", "This is the file 'delta'.\nLine 2.\n" },
263 { "A/epsilon", "This is the file 'epsilon'.\n" },
264 { "A/mu", "Re-added file 'mu'.\n" },
265 { "A/B", 0 },
266 { "A/B/epsilon", "This is the file 'epsilon'.\n" },
267 { "A/B/lambda", "This is the file 'lambda'.\n" },
268 { "A/B/E", 0 },
269 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
270 { "A/B/E/beta", "This is the file 'beta'.\n" },
271 { "A/B/F", 0 },
272 { "A/B/Z", 0 },
273 { "A/B/Z/zeta", "This is the file 'zeta'.\n" },
274 { "A/D", 0 },
275 { "A/D/gamma", "This is the file 'gamma'.\n" },
276 { "A/D/G", 0 },
277 { "A/D/G/pi", "This is the file 'pi'.\n" },
278 { "A/D/G/rho", "Changed file 'rho'.\n" },
279 { "A/D/G2", 0 },
280 { "A/D/G2/pi", "This is the file 'pi'.\n" },
281 { "A/D/G2/rho", "Changed file 'rho'.\n" },
282 { "A/D/H", 0 },
283 { "A/D/H/chi", "This is the file 'chi'.\n" },
284 { "A/D/H/psi", "This is the file 'psi'.\n" },
285 { "A/D/H/omega", 0 }
287 expected_trees[revision_count].entries = expected_entries;
288 expected_trees[revision_count].num_entries = 25;
289 SVN_ERR(svn_fs_revision_root(&revision_root, fs,
290 youngest_rev, pool));
291 SVN_ERR(svn_test__validate_tree
292 (revision_root, expected_trees[revision_count].entries,
293 expected_trees[revision_count].num_entries, subpool));
294 revision_count++;
296 svn_pool_clear(subpool);
298 /* THE BIG IDEA: Now that we have a collection of revisions, let's
299 first make sure that given any two revisions, we can get the
300 right delta between them. We'll do this by selecting our two
301 revisions, R1 and R2, basing a transaction off R1, deltafying the
302 txn with respect to R2, and then making sure our final txn looks
303 exactly like R2. This should work regardless of the
304 chronological order in which R1 and R2 were created. */
305 for (i = 0; i < revision_count; i++)
307 for (j = 0; j < revision_count; j++)
309 /* Prepare a txn that will receive the changes from
310 svn_repos_dir_delta2 */
311 SVN_ERR(svn_fs_begin_txn(&txn, fs, i, subpool));
312 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
314 /* Get the editor that will be modifying our transaction. */
315 SVN_ERR(dir_delta_get_editor(&editor,
316 &edit_baton,
318 txn_root,
320 subpool));
322 /* Here's the kicker...do the directory delta. */
323 SVN_ERR(svn_fs_revision_root(&revision_root, fs, j, subpool));
324 SVN_ERR(svn_repos_dir_delta2(txn_root,
327 revision_root,
329 editor,
330 edit_baton,
331 NULL,
332 NULL,
333 TRUE,
334 svn_depth_infinity,
335 FALSE,
336 FALSE,
337 subpool));
339 /* Hopefully at this point our transaction has been modified
340 to look exactly like our latest revision. We'll check
341 that. */
342 SVN_ERR(svn_test__validate_tree
343 (txn_root, expected_trees[j].entries,
344 expected_trees[j].num_entries, subpool));
346 /* We don't really want to do anything with this
347 transaction...so we'll abort it (good for software, bad
348 bad bad for society). */
349 svn_error_clear(svn_fs_abort_txn(txn, subpool));
350 svn_pool_clear(subpool);
354 svn_pool_destroy(subpool);
356 return SVN_NO_ERROR;
360 static svn_error_t *
361 node_tree_delete_under_copy(const char **msg,
362 svn_boolean_t msg_only,
363 svn_test_opts_t *opts,
364 apr_pool_t *pool)
366 svn_repos_t *repos;
367 svn_fs_t *fs;
368 svn_fs_txn_t *txn;
369 svn_fs_root_t *txn_root, *revision_root, *revision_2_root;
370 svn_revnum_t youngest_rev;
371 void *edit_baton;
372 const svn_delta_editor_t *editor;
373 svn_repos_node_t *tree;
374 apr_pool_t *subpool = svn_pool_create(pool);
376 *msg = "test deletions under copies in node_tree code";
378 if (msg_only)
379 return SVN_NO_ERROR;
381 /* Create a filesystem and repository. */
382 SVN_ERR(svn_test__create_repos(&repos, "test-repo-del-under-copy",
383 opts->fs_type, pool));
384 fs = svn_repos_fs(repos);
386 /* Prepare a txn to receive the greek tree. */
387 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
388 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
390 /* Create and commit the greek tree. */
391 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
392 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
394 /* Now, commit again, this time after copying a directory, and then
395 deleting some paths under that directory. */
396 SVN_ERR(svn_fs_revision_root(&revision_root, fs, youngest_rev, pool));
397 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
398 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
399 SVN_ERR(svn_fs_copy(revision_root, "A", txn_root, "Z", pool));
400 SVN_ERR(svn_fs_delete(txn_root, "Z/D/G/rho", pool));
401 SVN_ERR(svn_fs_delete(txn_root, "Z/D/H", pool));
402 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool));
404 /* Now, we run the node_tree editor code, and see that a) it doesn't
405 bomb out, and b) that our nodes are all good. */
406 SVN_ERR(svn_fs_revision_root(&revision_2_root, fs, youngest_rev, pool));
407 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
408 revision_root, revision_2_root,
409 pool, subpool));
410 SVN_ERR(svn_repos_replay2(revision_2_root, "", SVN_INVALID_REVNUM, FALSE,
411 editor, edit_baton, NULL, NULL, subpool));
413 /* Get the root of the generated tree, and cleanup our mess. */
414 tree = svn_repos_node_from_baton(edit_baton);
415 svn_pool_destroy(subpool);
417 /* See that we got what we expected (fortunately, svn_repos_replay
418 drivers editor paths in a predictable fashion!). */
420 if (! (tree /* / */
421 && tree->child /* /Z */
422 && tree->child->child /* /Z/D */
423 && tree->child->child->child /* /Z/D/G */
424 && tree->child->child->child->child /* /Z/D/G/rho */
425 && tree->child->child->child->sibling)) /* /Z/D/H */
426 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
427 "Generated node tree is bogus.");
429 if (! ((strcmp(tree->name, "") == 0)
430 && (strcmp(tree->child->name, "Z") == 0)
431 && (strcmp(tree->child->child->name, "D") == 0)
432 && (strcmp(tree->child->child->child->name, "G") == 0)
433 && ((strcmp(tree->child->child->child->child->name, "rho") == 0)
434 && (tree->child->child->child->child->kind == svn_node_file)
435 && (tree->child->child->child->child->action == 'D'))
436 && ((strcmp(tree->child->child->child->sibling->name, "H") == 0)
437 && (tree->child->child->child->sibling->kind == svn_node_dir)
438 && (tree->child->child->child->sibling->action == 'D'))))
439 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
440 "Generated node tree is bogus.");
442 return SVN_NO_ERROR;
446 /* Helper for revisions_changed(). */
447 static const char *
448 print_chrevs(const apr_array_header_t *revs_got,
449 int num_revs_expected,
450 const svn_revnum_t *revs_expected,
451 apr_pool_t *pool)
453 int i;
454 const char *outstr;
455 svn_revnum_t rev;
457 outstr = apr_psprintf(pool, "Got: { ");
458 if (revs_got)
460 for (i = 0; i < revs_got->nelts; i++)
462 rev = APR_ARRAY_IDX(revs_got, i, svn_revnum_t);
463 outstr = apr_pstrcat(pool,
464 outstr,
465 apr_psprintf(pool, "%ld ", rev),
466 NULL);
469 outstr = apr_pstrcat(pool, outstr, "} Expected: { ", NULL);
470 for (i = 0; i < num_revs_expected; i++)
472 outstr = apr_pstrcat(pool,
473 outstr,
474 apr_psprintf(pool, "%ld ",
475 revs_expected[i]),
476 NULL);
478 return apr_pstrcat(pool, outstr, "}", NULL);
482 /* Implements svn_repos_history_func_t interface. Accumulate history
483 revisions the apr_array_header_t * which is the BATON. */
484 static svn_error_t *
485 history_to_revs_array(void *baton,
486 const char *path,
487 svn_revnum_t revision,
488 apr_pool_t *pool)
490 apr_array_header_t *revs_array = baton;
491 APR_ARRAY_PUSH(revs_array, svn_revnum_t) = revision;
492 return SVN_NO_ERROR;
495 struct revisions_changed_results
497 const char *path;
498 int num_revs;
499 svn_revnum_t revs_changed[11];
503 static svn_error_t *
504 revisions_changed(const char **msg,
505 svn_boolean_t msg_only,
506 svn_test_opts_t *opts,
507 apr_pool_t *pool)
509 apr_pool_t *spool = svn_pool_create(pool);
510 svn_repos_t *repos;
511 svn_fs_t *fs;
512 svn_fs_txn_t *txn;
513 svn_fs_root_t *txn_root, *rev_root;
514 svn_revnum_t youngest_rev = 0;
516 *msg = "test svn_repos_history() (partially)";
518 if (msg_only)
519 return SVN_NO_ERROR;
521 /* Create a filesystem and repository. */
522 SVN_ERR(svn_test__create_repos(&repos, "test-repo-revisions-changed",
523 opts->fs_type, pool));
524 fs = svn_repos_fs(repos);
526 /*** Testing Algorithm ***
528 1. Create a greek tree in revision 1.
529 2. Make a series of new revisions, changing a file here and file
530 there.
531 3. Loop over each path in each revision, verifying that we get
532 the right revisions-changed array back from the filesystem.
535 /* Created the greek tree in revision 1. */
536 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
537 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
538 SVN_ERR(svn_test__create_greek_tree(txn_root, spool));
539 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
540 svn_pool_clear(spool);
542 /* Revision 2 - mu, alpha, omega */
543 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
544 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
545 SVN_ERR(svn_test__set_file_contents(txn_root, "A/mu", "2", spool));
546 SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", "2", spool));
547 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/omega", "2", spool));
548 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
549 svn_pool_clear(spool);
551 /* Revision 3 - iota, lambda, psi, omega */
552 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
553 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
554 SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "3", spool));
555 SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/lambda", "3", spool));
556 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/psi", "3", spool));
557 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/omega", "3", spool));
558 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
559 svn_pool_clear(spool);
561 /* Revision 4 - iota, beta, gamma, pi, rho */
562 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
563 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
564 SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "4", spool));
565 SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/beta", "4", spool));
566 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/gamma", "4", spool));
567 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/pi", "4", spool));
568 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/rho", "4", spool));
569 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
570 svn_pool_clear(spool);
572 /* Revision 5 - mu, alpha, tau, chi */
573 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
574 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
575 SVN_ERR(svn_test__set_file_contents(txn_root, "A/mu", "5", spool));
576 SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", "5", spool));
577 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/G/tau", "5", spool));
578 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "5", spool));
579 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
580 svn_pool_clear(spool);
582 /* Revision 6 - move A/D to A/Z */
583 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
584 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
585 SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool));
586 SVN_ERR(svn_fs_copy(rev_root, "A/D", txn_root, "A/Z", spool));
587 SVN_ERR(svn_fs_delete(txn_root, "A/D", spool));
588 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
589 svn_pool_clear(spool);
591 /* Revision 7 - edit A/Z/G/pi */
592 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
593 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
594 SVN_ERR(svn_test__set_file_contents(txn_root, "A/Z/G/pi", "7", spool));
595 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
596 svn_pool_clear(spool);
598 /* Revision 8 - move A/Z back to A/D, edit iota */
599 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
600 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
601 SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool));
602 SVN_ERR(svn_fs_copy(rev_root, "A/Z", txn_root, "A/D", spool));
603 SVN_ERR(svn_fs_delete(txn_root, "A/Z", spool));
604 SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "8", spool));
605 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
606 svn_pool_clear(spool);
608 /* Revision 9 - copy A/D/G to A/D/Q */
609 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
610 SVN_ERR(svn_fs_txn_root(&txn_root, txn, spool));
611 SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, spool));
612 SVN_ERR(svn_fs_copy(rev_root, "A/D/G", txn_root, "A/D/Q", spool));
613 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
614 svn_pool_clear(spool);
616 /* Revision 10 - edit A/D/Q/pi and A/D/Q/rho */
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_test__set_file_contents(txn_root, "A/D/Q/pi", "10", spool));
620 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/Q/rho", "10", spool));
621 SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, spool));
622 svn_pool_clear(spool);
624 /* Now, it's time to verify our results. */
626 int j;
627 /* Number, and list of, changed revisions for each path. Note
628 that for now, bubble-up in directories causes the directory to
629 appear changed though no entries were added or removed, and no
630 property mods occurred. Also note that this matrix represents
631 only the final state of the paths existing in HEAD of the
632 repository.
634 Notice for each revision, you can glance down that revision's
635 column in this table and see all the paths modified directory or
636 via bubble-up. */
637 static const struct revisions_changed_results test_data[25] = {
638 /* path, num, revisions changed... */
639 { "", 11, { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } },
640 { "iota", 4, { 8, 4, 3, 1 } },
641 { "A", 10, { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 } },
642 { "A/mu", 3, { 5, 2, 1 } },
643 { "A/B", 5, { 5, 4, 3, 2, 1 } },
644 { "A/B/lambda", 2, { 3, 1 } },
645 { "A/B/E", 4, { 5, 4, 2, 1 } },
646 { "A/B/E/alpha", 3, { 5, 2, 1 } },
647 { "A/B/E/beta", 2, { 4, 1 } },
648 { "A/B/F", 1, { 1 } },
649 { "A/C", 1, { 1 } },
650 { "A/D", 10, { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 } },
651 { "A/D/gamma", 4, { 8, 6, 4, 1 } },
652 { "A/D/G", 6, { 8, 7, 6, 5, 4, 1 } },
653 { "A/D/G/pi", 5, { 8, 7, 6, 4, 1 } },
654 { "A/D/G/rho", 4, { 8, 6, 4, 1 } },
655 { "A/D/G/tau", 4, { 8, 6, 5, 1 } },
656 { "A/D/Q", 8, { 10, 9, 8, 7, 6, 5, 4, 1 } },
657 { "A/D/Q/pi", 7, { 10, 9, 8, 7, 6, 4, 1 } },
658 { "A/D/Q/rho", 6, { 10, 9, 8, 6, 4, 1 } },
659 { "A/D/Q/tau", 5, { 9, 8, 6, 5, 1 } },
660 { "A/D/H", 6, { 8, 6, 5, 3, 2, 1 } },
661 { "A/D/H/chi", 4, { 8, 6, 5, 1 } },
662 { "A/D/H/psi", 4, { 8, 6, 3, 1 } },
663 { "A/D/H/omega", 5, { 8, 6, 3, 2, 1 } }
666 /* Now, for each path in the revision, get its changed-revisions
667 array and compare the array to the static results above. */
668 for (j = 0; j < 25; j++)
670 int i;
671 const char *path = test_data[j].path;
672 int num_revs = test_data[j].num_revs;
673 const svn_revnum_t *revs_changed = test_data[j].revs_changed;
674 apr_array_header_t *revs = apr_array_make(spool, 10,
675 sizeof(svn_revnum_t));
677 SVN_ERR(svn_repos_history(fs, path, history_to_revs_array, revs,
678 0, youngest_rev, TRUE, spool));
680 /* Are we at least looking at the right number of returned
681 revisions? */
682 if ((! revs) || (revs->nelts != num_revs))
683 return svn_error_createf
684 (SVN_ERR_FS_GENERAL, NULL,
685 "Changed revisions differ from expected for '%s'\n%s",
686 path, print_chrevs(revs, num_revs, revs_changed, spool));
688 /* Do the revisions lists match up exactly? */
689 for (i = 0; i < num_revs; i++)
691 svn_revnum_t rev = APR_ARRAY_IDX(revs, i, svn_revnum_t);
692 if (rev != revs_changed[i])
693 return svn_error_createf
694 (SVN_ERR_FS_GENERAL, NULL,
695 "Changed revisions differ from expected for '%s'\n%s",
696 path, print_chrevs(revs, num_revs, revs_changed, spool));
699 /* Clear the per-iteration subpool. */
700 svn_pool_clear(spool);
704 /* Destroy the subpool. */
705 svn_pool_destroy(spool);
707 return SVN_NO_ERROR;
712 struct locations_info
714 svn_revnum_t rev;
715 const char *path;
718 /* Check that LOCATIONS contain everything in INFO and nothing more. */
719 static svn_error_t *
720 check_locations_info(apr_hash_t *locations, const struct locations_info *info)
722 unsigned int i;
723 for (i = 0; info->rev != 0; ++i, ++info)
725 const char *p = apr_hash_get(locations, &info->rev, sizeof
726 (svn_revnum_t));
727 if (!p)
728 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
729 "Missing path for revision %ld", info->rev);
730 if (strcmp(p, info->path) != 0)
731 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
732 "Pth mismatch for rev %ld", info->rev);
735 if (apr_hash_count(locations) > i)
736 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
737 "Returned locations contain too many elements.");
739 return SVN_NO_ERROR;
742 /* Check that all locations in INFO exist in REPOS for PATH and PEG_REVISION.
744 static svn_error_t *
745 check_locations(svn_fs_t *fs, struct locations_info *info,
746 const char *path, svn_revnum_t peg_revision,
747 apr_pool_t *pool)
749 apr_array_header_t *a = apr_array_make(pool, 0, sizeof(svn_revnum_t));
750 apr_hash_t *h;
751 struct locations_info *iter;
753 for (iter = info; iter->rev != 0; ++iter)
754 APR_ARRAY_PUSH(a, svn_revnum_t) = iter->rev;
756 SVN_ERR(svn_repos_trace_node_locations(fs, &h, path, peg_revision, a,
757 NULL, NULL, pool));
758 SVN_ERR(check_locations_info(h, info));
760 return SVN_NO_ERROR;
763 static svn_error_t *
764 node_locations(const char **msg,
765 svn_boolean_t msg_only,
766 svn_test_opts_t *opts,
767 apr_pool_t *pool)
769 apr_pool_t *subpool = svn_pool_create(pool);
770 svn_repos_t *repos;
771 svn_fs_t *fs;
772 svn_fs_txn_t *txn;
773 svn_fs_root_t *txn_root, *root;
774 svn_revnum_t youngest_rev;
776 *msg = "test svn_repos_node_locations";
777 if (msg_only)
778 return SVN_NO_ERROR;
780 /* Create the repository with a Greek tree. */
781 SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations",
782 opts->fs_type, pool));
783 fs = svn_repos_fs(repos);
784 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
785 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
786 SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
787 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
788 svn_pool_clear(subpool);
790 /* Move a file. Rev 2. */
791 SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool));
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_fs_copy(root, "/A/mu", txn_root, "/mu.new", subpool));
795 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
797 struct locations_info info[] =
799 { 1, "/A/mu" },
800 { 2, "/mu.new" },
801 { 0 }
804 /* Test this twice, once with a leading slash, once without,
805 because we know that the "without" form has caused us trouble
806 in the past. */
807 SVN_ERR(check_locations(fs, info, "/mu.new", 2, pool));
808 SVN_ERR(check_locations(fs, info, "mu.new", 2, pool));
810 svn_pool_clear(subpool);
812 return SVN_NO_ERROR;
816 static svn_error_t *
817 node_locations2(const char **msg,
818 svn_boolean_t msg_only,
819 svn_test_opts_t *opts,
820 apr_pool_t *pool)
822 apr_pool_t *subpool = svn_pool_create(pool);
823 svn_repos_t *repos;
824 svn_fs_t *fs;
825 svn_fs_txn_t *txn;
826 svn_fs_root_t *txn_root, *root;
827 svn_revnum_t youngest_rev = 0;
829 *msg = "test svn_repos_node_locations some more";
830 if (msg_only)
831 return SVN_NO_ERROR;
833 /* Create the repository. */
834 SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations2",
835 opts->fs_type, pool));
836 fs = svn_repos_fs(repos);
838 /* Revision 1: Add a directory /foo */
839 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
840 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
841 SVN_ERR(svn_fs_make_dir(txn_root, "/foo", subpool));
842 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
843 svn_pool_clear(subpool);
845 /* Revision 2: Copy /foo to /bar, and add /bar/baz */
846 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
847 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
848 SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool));
849 SVN_ERR(svn_fs_copy(root, "/foo", txn_root, "/bar", subpool));
850 SVN_ERR(svn_fs_make_file(txn_root, "/bar/baz", subpool));
851 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
852 svn_pool_clear(subpool);
854 /* Revision 3: Modify /bar/baz */
855 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
856 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
857 SVN_ERR(svn_test__set_file_contents(txn_root, "/bar/baz", "brrt", subpool));
858 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
859 svn_pool_clear(subpool);
861 /* Revision 4: Modify /bar/baz again */
862 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
863 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
864 SVN_ERR(svn_test__set_file_contents(txn_root, "/bar/baz", "bzzz", subpool));
865 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
866 svn_pool_clear(subpool);
868 /* Now, check locations. */
870 struct locations_info info[] =
872 { 3, "/bar/baz" },
873 { 2, "/bar/baz" },
874 { 0 }
876 SVN_ERR(check_locations(fs, info, "/bar/baz", youngest_rev, pool));
879 return SVN_NO_ERROR;
884 /* Testing the reporter. */
886 /* Functions for an editor that will catch removal of defunct locks. */
888 /* The main editor baton. */
889 typedef struct rmlocks_baton_t {
890 apr_hash_t *removed;
891 apr_pool_t *pool;
892 } rmlocks_baton_t;
894 /* The file baton. */
895 typedef struct rmlocks_file_baton_t {
896 rmlocks_baton_t *main_baton;
897 const char *path;
898 } rmlocks_file_baton_t;
900 /* An svn_delta_editor_t function. */
901 static svn_error_t *
902 rmlocks_open_file(const char *path,
903 void *parent_baton,
904 svn_revnum_t base_revision,
905 apr_pool_t *file_pool,
906 void **file_baton)
908 rmlocks_file_baton_t *fb = apr_palloc(file_pool, sizeof(*fb));
909 rmlocks_baton_t *b = parent_baton;
911 fb->main_baton = b;
912 fb->path = apr_pstrdup(b->pool, path);
914 *file_baton = fb;
916 return SVN_NO_ERROR;
919 /* An svn_delta_editor_t function. */
920 static svn_error_t *
921 rmlocks_change_prop(void *file_baton,
922 const char *name,
923 const svn_string_t *value,
924 apr_pool_t *pool)
926 rmlocks_file_baton_t *fb = file_baton;
928 if (strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) == 0)
930 if (value != NULL)
931 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
932 "Value for lock-token property not NULL");
934 /* We only want it removed once. */
935 if (apr_hash_get(fb->main_baton->removed, fb->path,
936 APR_HASH_KEY_STRING) != NULL)
937 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
938 "Lock token for '%s' already removed",
939 fb->path);
941 /* Mark as removed. */
942 apr_hash_set(fb->main_baton->removed, fb->path, APR_HASH_KEY_STRING,
943 (void *)1);
946 return SVN_NO_ERROR;
949 /* An svn_delta_editor_t function. */
950 static svn_error_t *
951 rmlocks_open_root(void *edit_baton,
952 svn_revnum_t base_revision,
953 apr_pool_t *dir_pool,
954 void **root_baton)
956 *root_baton = edit_baton;
957 return SVN_NO_ERROR;
960 /* An svn_delta_editor_t function. */
961 static svn_error_t *
962 rmlocks_open_directory(const char *path,
963 void *parent_baton,
964 svn_revnum_t base_revision,
965 apr_pool_t *pool,
966 void **dir_baton)
968 *dir_baton = parent_baton;
969 return SVN_NO_ERROR;
972 /* Create an svn_delta_editor/baton, storing them in EDITOR/EDIT_BATON,
973 that will store paths for which lock tokens were *REMOVED in REMOVED.
974 Allocate the editor and *REMOVED in POOL. */
975 static svn_error_t *
976 create_rmlocks_editor(svn_delta_editor_t **editor,
977 void **edit_baton,
978 apr_hash_t **removed,
979 apr_pool_t *pool)
981 rmlocks_baton_t *baton = apr_palloc(pool, sizeof(*baton));
983 /* Create the editor. */
984 *editor = svn_delta_default_editor(pool);
985 (*editor)->open_root = rmlocks_open_root;
986 (*editor)->open_directory = rmlocks_open_directory;
987 (*editor)->open_file = rmlocks_open_file;
988 (*editor)->change_file_prop = rmlocks_change_prop;
990 /* Initialize the baton. */
991 baton->removed = apr_hash_make(pool);
992 baton->pool = pool;
993 *edit_baton = baton;
995 *removed = baton->removed;
997 return SVN_NO_ERROR;
1000 /* Check that HASH contains exactly the const char * entries for all entries
1001 in the NULL-terminated array SPEC. */
1002 static svn_error_t *
1003 rmlocks_check(const char **spec, apr_hash_t *hash)
1005 apr_size_t n = 0;
1007 for (; *spec; ++spec, ++n)
1009 if (! apr_hash_get(hash, *spec, APR_HASH_KEY_STRING))
1010 return svn_error_createf
1011 (SVN_ERR_TEST_FAILED, NULL,
1012 "Lock token for '%s' should have been removed", *spec);
1015 if (n < apr_hash_count(hash))
1016 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
1017 "Lock token for one or more paths unexpectedly "
1018 "removed");
1019 return SVN_NO_ERROR;
1022 /* Test that defunct locks are removed by the reporter. */
1023 static svn_error_t *
1024 rmlocks(const char **msg,
1025 svn_boolean_t msg_only,
1026 svn_test_opts_t *opts,
1027 apr_pool_t *pool)
1029 svn_repos_t *repos;
1030 svn_fs_t *fs;
1031 svn_fs_txn_t *txn;
1032 svn_fs_root_t *txn_root;
1033 apr_pool_t *subpool = svn_pool_create(pool);
1034 svn_revnum_t youngest_rev;
1035 svn_delta_editor_t *editor;
1036 void *edit_baton, *report_baton;
1037 svn_lock_t *l1, *l2, *l3, *l4;
1038 svn_fs_access_t *fs_access;
1039 apr_hash_t *removed;
1041 *msg = "test removal of defunct locks";
1043 if (msg_only)
1044 return SVN_NO_ERROR;
1046 /* Create a filesystem and repository. */
1047 SVN_ERR(svn_test__create_repos(&repos, "test-repo-rmlocks",
1048 opts->fs_type, pool));
1049 fs = svn_repos_fs(repos);
1051 /* Prepare a txn to receive the greek tree. */
1052 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
1053 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1054 SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
1055 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1056 svn_pool_clear(subpool);
1058 SVN_ERR(svn_fs_create_access(&fs_access, "user1", pool));
1059 SVN_ERR(svn_fs_set_access(fs, fs_access));
1061 /* Lock some files, break a lock, steal another and check that those get
1062 removed. */
1064 const char *expected [] = { "A/mu", "A/D/gamma", NULL };
1066 SVN_ERR(svn_fs_lock(&l1, fs, "/iota", NULL, NULL, 0, 0, youngest_rev,
1067 FALSE, subpool));
1068 SVN_ERR(svn_fs_lock(&l2, fs, "/A/mu", NULL, NULL, 0, 0, youngest_rev,
1069 FALSE, subpool));
1070 SVN_ERR(svn_fs_lock(&l3, fs, "/A/D/gamma", NULL, NULL, 0, 0, youngest_rev,
1071 FALSE, subpool));
1073 /* Break l2. */
1074 SVN_ERR(svn_fs_unlock(fs, "/A/mu", NULL, TRUE, subpool));
1076 /* Steal l3 from ourselves. */
1077 SVN_ERR(svn_fs_lock(&l4, fs, "/A/D/gamma", NULL, NULL, 0, 0, youngest_rev,
1078 TRUE, subpool));
1080 /* Create the editor. */
1081 SVN_ERR(create_rmlocks_editor(&editor, &edit_baton, &removed, subpool));
1083 /* Report what we have. */
1084 SVN_ERR(svn_repos_begin_report2(&report_baton, 1, repos, "/", "", NULL,
1085 FALSE, svn_depth_infinity, FALSE, FALSE,
1086 editor, edit_baton, NULL, NULL, subpool));
1087 SVN_ERR(svn_repos_set_path3(report_baton, "", 1,
1088 svn_depth_infinity,
1089 FALSE, NULL, subpool));
1090 SVN_ERR(svn_repos_set_path3(report_baton, "iota", 1,
1091 svn_depth_infinity,
1092 FALSE, l1->token, subpool));
1093 SVN_ERR(svn_repos_set_path3(report_baton, "A/mu", 1,
1094 svn_depth_infinity,
1095 FALSE, l2->token, subpool));
1096 SVN_ERR(svn_repos_set_path3(report_baton, "A/D/gamma", 1,
1097 svn_depth_infinity,
1098 FALSE, l3->token, subpool));
1100 /* End the report. */
1101 SVN_ERR(svn_repos_finish_report(report_baton, pool));
1103 /* And check that the edit did what we wanted. */
1104 SVN_ERR(rmlocks_check(expected, removed));
1107 svn_pool_destroy(subpool);
1109 return SVN_NO_ERROR;
1114 /* Helper for the authz test. Set *AUTHZ_P to a representation of
1115 AUTHZ_CONTENTS, using POOL for temporary allocation. */
1116 static svn_error_t *
1117 authz_get_handle(svn_authz_t **authz_p, const char *authz_contents,
1118 apr_pool_t *pool)
1120 apr_file_t *authz_file;
1121 apr_status_t apr_err;
1122 const char *authz_file_path;
1123 svn_error_t *err;
1125 /* Create a temporary file, and fetch its name. */
1126 SVN_ERR_W(svn_io_open_unique_file2(&authz_file, &authz_file_path,
1127 "authz_file", "tmp",
1128 svn_io_file_del_none, pool),
1129 "Opening temporary file");
1131 /* Write the authz ACLs to the file. */
1132 if ((apr_err = apr_file_write_full(authz_file, authz_contents,
1133 strlen(authz_contents), NULL)))
1135 (void) apr_file_close(authz_file);
1136 (void) apr_file_remove(authz_file_path, pool);
1137 return svn_error_wrap_apr(apr_err, "Writing test authz file");
1140 /* Close the temporary descriptor. */
1141 if ((apr_err = apr_file_close(authz_file)))
1143 (void) apr_file_remove(authz_file_path, pool);
1144 return svn_error_wrap_apr(apr_err, "Closing test authz file");
1147 /* Read the authz configuration back and start testing. */
1148 if ((err = svn_repos_authz_read(authz_p, authz_file_path, TRUE, pool)))
1150 (void) apr_file_remove(authz_file_path, pool);
1151 return svn_error_quick_wrap(err, "Opening test authz file");
1154 /* Delete the file, but ignore the error if we've a more important one. */
1155 if ((apr_err = apr_file_remove(authz_file_path, pool)))
1156 return svn_error_wrap_apr(apr_err, "Removing test authz file");
1158 return SVN_NO_ERROR;
1163 /* Test that authz is giving out the right authorizations. */
1164 static svn_error_t *
1165 authz(const char **msg,
1166 svn_boolean_t msg_only,
1167 svn_test_opts_t *opts,
1168 apr_pool_t *pool)
1170 const char *contents;
1171 svn_authz_t *authz_cfg;
1172 svn_error_t *err;
1173 svn_boolean_t access_granted;
1174 apr_pool_t *subpool = svn_pool_create(pool);
1175 int i;
1176 /* Definition of the paths to test and expected replies for each. */
1177 struct
1179 const char *path;
1180 const char *user;
1181 const svn_repos_authz_access_t required;
1182 const svn_boolean_t expected;
1183 } test_set[] = {
1184 /* Test that read rules are correctly used. */
1185 { "/A", NULL, svn_authz_read, TRUE },
1186 { "/iota", NULL, svn_authz_read, FALSE },
1187 /* Test that write rules are correctly used. */
1188 { "/A", "plato", svn_authz_write, TRUE },
1189 { "/A", NULL, svn_authz_write, FALSE },
1190 /* Test that pan-repository rules are found and used. */
1191 { "/A/B/lambda", "plato", svn_authz_read, TRUE },
1192 { "/A/B/lambda", NULL, svn_authz_read, FALSE },
1193 /* Test that authz uses parent path ACLs if no rule for the path
1194 exists. */
1195 { "/A/C", NULL, svn_authz_read, TRUE },
1196 /* Test that recursive access requests take into account the rules
1197 of subpaths. */
1198 { "/A/D", "plato", svn_authz_read | svn_authz_recursive, TRUE },
1199 { "/A/D", NULL, svn_authz_read | svn_authz_recursive, FALSE },
1200 /* Test global write access lookups. */
1201 { NULL, "plato", svn_authz_read, TRUE },
1202 { NULL, NULL, svn_authz_write, FALSE },
1203 /* Sentinel */
1204 { NULL, NULL, svn_authz_none, FALSE }
1207 *msg = "test authz access control";
1209 if (msg_only)
1210 return SVN_NO_ERROR;
1212 /* The test logic:
1214 * 1. Perform various access tests on a set of authz rules. Each
1215 * test has a known outcome and tests different aspects of authz,
1216 * such as inheriting parent-path authz, pan-repository rules or
1217 * recursive access. 'plato' is our friendly neighborhood user with
1218 * more access rights than other anonymous philosophers.
1220 * 2. Load an authz file containing a cyclic dependency in groups
1221 * and another containing a reference to an undefined group. Verify
1222 * that svn_repos_authz_read fails to load both and returns an
1223 * "invalid configuration" error.
1225 * 3. Regression test for a bug in how recursion is handled in
1226 * authz. The bug was that paths not under the parent path
1227 * requested were being considered during the determination of
1228 * access rights (eg. a rule for /dir2 matched during a lookup for
1229 * /dir), due to incomplete tests on path relations.
1232 /* The authz rules for the phase 1 tests. */
1233 contents =
1234 "[greek:/A]" NL
1235 "* = r" NL
1236 "plato = w" NL
1237 "" NL
1238 "[greek:/iota]" NL
1239 "* =" NL
1240 "" NL
1241 "[/A/B/lambda]" NL
1242 "plato = r" NL
1243 "* =" NL
1244 "" NL
1245 "[greek:/A/D]" NL
1246 "plato = r" NL
1247 "* = r" NL
1248 "" NL
1249 "[greek:/A/D/G]" NL
1250 "plato = r" NL
1251 "* =" NL
1252 "" NL
1253 "[greek:/A/B/E/beta]" NL
1254 "* =" NL
1255 "" NL
1256 "[/nowhere]" NL
1257 "nobody = r" NL
1258 "" NL;
1260 /* Load the test authz rules. */
1261 SVN_ERR(authz_get_handle(&authz_cfg, contents, subpool));
1263 /* Loop over the test array and test each case. */
1264 for (i = 0; !(test_set[i].path == NULL
1265 && test_set[i].required == svn_authz_none); i++)
1267 SVN_ERR(svn_repos_authz_check_access(authz_cfg, "greek",
1268 test_set[i].path,
1269 test_set[i].user,
1270 test_set[i].required,
1271 &access_granted, subpool));
1273 if (access_granted != test_set[i].expected)
1275 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
1276 "Authz incorrectly %s %s%s access "
1277 "to greek:%s for user %s",
1278 access_granted ?
1279 "grants" : "denies",
1280 test_set[i].required
1281 & svn_authz_recursive ?
1282 "recursive " : "",
1283 test_set[i].required
1284 & svn_authz_read ?
1285 "read" : "write",
1286 test_set[i].path,
1287 test_set[i].user ?
1288 test_set[i].user : "-");
1293 /* The authz rules for the phase 2 tests, first case (cyclic
1294 dependency). */
1295 contents =
1296 "[groups]" NL
1297 "slaves = cooks,scribes,@gladiators" NL
1298 "gladiators = equites,thraces,@slaves" NL
1299 "" NL
1300 "[greek:/A]" NL
1301 "@slaves = r" NL;
1303 /* Load the test authz rules and check that group cycles are
1304 reported. */
1305 err = authz_get_handle(&authz_cfg, contents, subpool);
1306 if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
1307 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1308 "Got %s error instead of expected "
1309 "SVN_ERR_AUTHZ_INVALID_CONFIG",
1310 err ? "unexpected" : "no");
1311 svn_error_clear(err);
1313 /* The authz rules for the phase 2 tests, second case (missing group
1314 definition). */
1315 contents =
1316 "[greek:/A]" NL
1317 "@senate = r" NL;
1319 /* Check that references to undefined groups are reported. */
1320 err = authz_get_handle(&authz_cfg, contents, subpool);
1321 if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG)
1322 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1323 "Got %s error instead of expected "
1324 "SVN_ERR_AUTHZ_INVALID_CONFIG",
1325 err ? "unexpected" : "no");
1326 svn_error_clear(err);
1328 /* The authz rules for the phase 3 tests */
1329 contents =
1330 "[/]" NL
1331 "* = rw" NL
1332 "" NL
1333 "[greek:/dir2/secret]" NL
1334 "* =" NL;
1336 /* Load the test authz rules. */
1337 SVN_ERR(authz_get_handle(&authz_cfg, contents, subpool));
1339 /* Verify that the rule on /dir2/secret doesn't affect this
1340 request */
1341 SVN_ERR(svn_repos_authz_check_access(authz_cfg, "greek",
1342 "/dir", NULL,
1343 (svn_authz_read
1344 | svn_authz_recursive),
1345 &access_granted, subpool));
1346 if (!access_granted)
1347 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
1348 "Regression: incomplete ancestry test "
1349 "for recursive access lookup.");
1351 /* That's a wrap! */
1352 svn_pool_destroy(subpool);
1353 return SVN_NO_ERROR;
1358 /* Callback for the commit editor tests that relays requests to
1359 authz. */
1360 static svn_error_t *
1361 commit_authz_cb(svn_repos_authz_access_t required,
1362 svn_boolean_t *allowed,
1363 svn_fs_root_t *root,
1364 const char *path,
1365 void *baton,
1366 apr_pool_t *pool)
1368 svn_authz_t *authz_file = baton;
1370 return svn_repos_authz_check_access(authz_file, "test", path,
1371 "plato", required, allowed,
1372 pool);
1377 /* Test that the commit editor is taking authz into account
1378 properly */
1379 static svn_error_t *
1380 commit_editor_authz(const char **msg,
1381 svn_boolean_t msg_only,
1382 svn_test_opts_t *opts,
1383 apr_pool_t *pool)
1385 svn_repos_t *repos;
1386 svn_fs_t *fs;
1387 svn_fs_txn_t *txn;
1388 svn_fs_root_t *txn_root;
1389 svn_revnum_t youngest_rev;
1390 void *edit_baton;
1391 void *root_baton, *dir_baton, *dir2_baton, *file_baton;
1392 svn_error_t *err;
1393 const svn_delta_editor_t *editor;
1394 svn_authz_t *authz_file;
1395 apr_pool_t *subpool = svn_pool_create(pool);
1396 const char *authz_contents;
1398 *msg = "test authz in the commit editor";
1400 if (msg_only)
1401 return SVN_NO_ERROR;
1403 /* The Test Plan
1405 * We create a greek tree repository, then create a commit editor
1406 * and try to perform various operations that will run into authz
1407 * callbacks. Check that all operations are properly
1408 * authorized/denied when necessary. We don't try to be exhaustive
1409 * in the kinds of authz lookups. We just make sure that the editor
1410 * replies to the calls in a way that proves it is doing authz
1411 * lookups.
1413 * Note that this use of the commit editor is not kosher according
1414 * to the generic editor API (we aren't allowed to continue editing
1415 * after an error, nor are we allowed to assume that errors are
1416 * returned by the operations which caused them). But it should
1417 * work fine with this particular editor implementation.
1420 /* Create a filesystem and repository. */
1421 SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-authz",
1422 opts->fs_type, subpool));
1423 fs = svn_repos_fs(repos);
1425 /* Prepare a txn to receive the greek tree. */
1426 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
1427 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1428 SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
1429 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1431 /* Load the authz rules for the greek tree. */
1432 authz_contents =
1433 "" NL
1434 "" NL
1435 "[/]" NL
1436 "plato = r" NL
1437 "" NL
1438 "[/A]" NL
1439 "plato = rw" NL
1440 "" NL
1441 "[/A/alpha]" NL
1442 "plato = " NL
1443 "" NL
1444 "[/A/C]" NL
1445 "" NL
1446 "plato = " NL
1447 "" NL
1448 "[/A/D]" NL
1449 "plato = rw" NL
1450 "" NL
1451 "[/A/D/G]" NL
1452 "plato = r"; /* No newline at end of file. */
1454 SVN_ERR(authz_get_handle(&authz_file, authz_contents, subpool));
1456 /* Create a new commit editor in which we're going to play with
1457 authz */
1458 SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos,
1459 NULL, "file://test", "/",
1460 "plato", "test commit", NULL,
1461 NULL, commit_authz_cb, authz_file,
1462 subpool));
1464 /* Start fiddling. First get the root, which is readonly. All
1465 write operations fail because of the root's permissions. */
1466 SVN_ERR(editor->open_root(edit_baton, 1, subpool, &root_baton));
1468 /* Test denied file deletion. */
1469 err = editor->delete_entry("/iota", SVN_INVALID_REVNUM, root_baton, subpool);
1470 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1471 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1472 "Got %s error instead of expected "
1473 "SVN_ERR_AUTHZ_UNWRITABLE",
1474 err ? "unexpected" : "no");
1475 svn_error_clear(err);
1477 /* Test authorized file open. */
1478 SVN_ERR(editor->open_file("/iota", root_baton, SVN_INVALID_REVNUM,
1479 subpool, &file_baton));
1481 /* Test unauthorized file prop set. */
1482 err = editor->change_file_prop(file_baton, "svn:test",
1483 svn_string_create("test", subpool),
1484 subpool);
1485 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1486 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1487 "Got %s error instead of expected "
1488 "SVN_ERR_AUTHZ_UNWRITABLE",
1489 err ? "unexpected" : "no");
1490 svn_error_clear(err);
1492 /* Test denied file addition. */
1493 err = editor->add_file("/alpha", root_baton, NULL, SVN_INVALID_REVNUM,
1494 subpool, &file_baton);
1495 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1496 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1497 "Got %s error instead of expected "
1498 "SVN_ERR_AUTHZ_UNWRITABLE",
1499 err ? "unexpected" : "no");
1500 svn_error_clear(err);
1502 /* Test denied file copy. */
1503 err = editor->add_file("/alpha", root_baton, "file://test/A/B/lambda",
1504 youngest_rev, subpool, &file_baton);
1505 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1506 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1507 "Got %s error instead of expected "
1508 "SVN_ERR_AUTHZ_UNWRITABLE",
1509 err ? "unexpected" : "no");
1510 svn_error_clear(err);
1512 /* Test denied directory addition. */
1513 err = editor->add_directory("/I", root_baton, NULL,
1514 SVN_INVALID_REVNUM, subpool, &dir_baton);
1515 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1516 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1517 "Got %s error instead of expected "
1518 "SVN_ERR_AUTHZ_UNWRITABLE",
1519 err ? "unexpected" : "no");
1520 svn_error_clear(err);
1522 /* Test denied directory copy. */
1523 err = editor->add_directory("/J", root_baton, "file://test/A/D",
1524 youngest_rev, subpool, &dir_baton);
1525 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1526 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1527 "Got %s error instead of expected "
1528 "SVN_ERR_AUTHZ_UNWRITABLE",
1529 err ? "unexpected" : "no");
1530 svn_error_clear(err);
1532 /* Open directory /A, to which we have read/write access. */
1533 SVN_ERR(editor->open_directory("/A", root_baton,
1534 SVN_INVALID_REVNUM,
1535 subpool, &dir_baton));
1537 /* Test denied file addition. Denied because of a conflicting rule
1538 on the file path itself. */
1539 err = editor->add_file("/A/alpha", dir_baton, NULL,
1540 SVN_INVALID_REVNUM, subpool, &file_baton);
1541 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1542 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1543 "Got %s error instead of expected "
1544 "SVN_ERR_AUTHZ_UNWRITABLE",
1545 err ? "unexpected" : "no");
1546 svn_error_clear(err);
1548 /* Test authorized file addition. */
1549 SVN_ERR(editor->add_file("/A/B/theta", dir_baton, NULL,
1550 SVN_INVALID_REVNUM, subpool,
1551 &file_baton));
1553 /* Test authorized file deletion. */
1554 SVN_ERR(editor->delete_entry("/A/mu", SVN_INVALID_REVNUM, dir_baton,
1555 subpool));
1557 /* Test authorized directory creation. */
1558 SVN_ERR(editor->add_directory("/A/E", dir_baton, NULL,
1559 SVN_INVALID_REVNUM, subpool,
1560 &dir2_baton));
1562 /* Test authorized copy of a tree. */
1563 SVN_ERR(editor->add_directory("/A/J", dir_baton, "file://test/A/D",
1564 youngest_rev, subpool,
1565 &dir2_baton));
1567 /* Open /A/D. This should be granted. */
1568 SVN_ERR(editor->open_directory("/A/D", dir_baton, SVN_INVALID_REVNUM,
1569 subpool, &dir_baton));
1571 /* Test denied recursive deletion. */
1572 err = editor->delete_entry("/A/D/G", SVN_INVALID_REVNUM, dir_baton,
1573 subpool);
1574 if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE)
1575 return svn_error_createf(SVN_ERR_TEST_FAILED, err,
1576 "Got %s error instead of expected "
1577 "SVN_ERR_AUTHZ_UNWRITABLE",
1578 err ? "unexpected" : "no");
1579 svn_error_clear(err);
1581 /* Test authorized recursive deletion. */
1582 SVN_ERR(editor->delete_entry("/A/D/H", SVN_INVALID_REVNUM,
1583 dir_baton, subpool));
1585 /* Test authorized propset (open the file first). */
1586 SVN_ERR(editor->open_file("/A/D/gamma", dir_baton, SVN_INVALID_REVNUM,
1587 subpool, &file_baton));
1588 SVN_ERR(editor->change_file_prop(file_baton, "svn:test",
1589 svn_string_create("test", subpool),
1590 subpool));
1592 /* Done. */
1593 SVN_ERR(editor->abort_edit(edit_baton, subpool));
1594 svn_pool_destroy(subpool);
1596 return SVN_NO_ERROR;
1599 /* This implements svn_commit_callback2_t. */
1600 static svn_error_t *
1601 dummy_commit_cb(const svn_commit_info_t *commit_info,
1602 void *baton, apr_pool_t *pool)
1604 return SVN_NO_ERROR;
1607 /* Test using explicit txns during a commit. */
1608 static svn_error_t *
1609 commit_continue_txn(const char **msg,
1610 svn_boolean_t msg_only,
1611 svn_test_opts_t *opts,
1612 apr_pool_t *pool)
1614 svn_repos_t *repos;
1615 svn_fs_t *fs;
1616 svn_fs_txn_t *txn;
1617 svn_fs_root_t *txn_root, *revision_root;
1618 svn_revnum_t youngest_rev;
1619 void *edit_baton;
1620 void *root_baton, *file_baton;
1621 const svn_delta_editor_t *editor;
1622 apr_pool_t *subpool = svn_pool_create(pool);
1623 const char *txn_name;
1625 *msg = "test commit with explicit txn";
1627 if (msg_only)
1628 return SVN_NO_ERROR;
1630 /* The Test Plan
1632 * We create a greek tree repository, then create a transaction and
1633 * a commit editor from that txn. We do one change, abort the edit, reopen
1634 * the txn and create a new commit editor, do anyther change and commit.
1635 * We check that both changes were done.
1638 /* Create a filesystem and repository. */
1639 SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-continue",
1640 opts->fs_type, subpool));
1641 fs = svn_repos_fs(repos);
1643 /* Prepare a txn to receive the greek tree. */
1644 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
1645 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1646 SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
1647 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1649 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
1650 SVN_ERR(svn_fs_txn_name(&txn_name, txn, subpool));
1651 SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos,
1652 txn, "file://test", "/",
1653 "plato", "test commit",
1654 dummy_commit_cb, NULL, NULL, NULL,
1655 subpool));
1657 SVN_ERR(editor->open_root(edit_baton, 1, subpool, &root_baton));
1659 SVN_ERR(editor->add_file("/f1", root_baton, NULL, SVN_INVALID_REVNUM,
1660 subpool, &file_baton));
1661 SVN_ERR(editor->close_file(file_baton, NULL, subpool));
1662 /* This should leave the transaction. */
1663 SVN_ERR(editor->abort_edit(edit_baton, subpool));
1665 /* Reopen the transaction. */
1666 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, subpool));
1667 SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos,
1668 txn, "file://test", "/",
1669 "plato", "test commit",
1670 dummy_commit_cb,
1671 NULL, NULL, NULL,
1672 subpool));
1674 SVN_ERR(editor->open_root(edit_baton, 1, subpool, &root_baton));
1676 SVN_ERR(editor->add_file("/f2", root_baton, NULL, SVN_INVALID_REVNUM,
1677 subpool, &file_baton));
1678 SVN_ERR(editor->close_file(file_baton, NULL, subpool));
1680 /* Finally, commit it. */
1681 SVN_ERR(editor->close_edit(edit_baton, subpool));
1683 /* Check that the edits really happened. */
1685 static svn_test__tree_entry_t expected_entries[] = {
1686 /* path, contents (0 = dir) */
1687 { "iota", "This is the file 'iota'.\n" },
1688 { "A", 0 },
1689 { "A/mu", "This is the file 'mu'.\n" },
1690 { "A/B", 0 },
1691 { "A/B/lambda", "This is the file 'lambda'.\n" },
1692 { "A/B/E", 0 },
1693 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
1694 { "A/B/E/beta", "This is the file 'beta'.\n" },
1695 { "A/B/F", 0 },
1696 { "A/C", 0 },
1697 { "A/D", 0 },
1698 { "A/D/gamma", "This is the file 'gamma'.\n" },
1699 { "A/D/G", 0 },
1700 { "A/D/G/pi", "This is the file 'pi'.\n" },
1701 { "A/D/G/rho", "This is the file 'rho'.\n" },
1702 { "A/D/G/tau", "This is the file 'tau'.\n" },
1703 { "A/D/H", 0 },
1704 { "A/D/H/chi", "This is the file 'chi'.\n" },
1705 { "A/D/H/psi", "This is the file 'psi'.\n" },
1706 { "A/D/H/omega", "This is the file 'omega'.\n" },
1707 { "f1", "" },
1708 { "f2", "" }
1710 SVN_ERR(svn_fs_revision_root(&revision_root, fs,
1711 2, subpool));
1712 SVN_ERR(svn_test__validate_tree
1713 (revision_root, expected_entries,
1714 sizeof(expected_entries) / sizeof(expected_entries[0]),
1715 subpool));
1718 svn_pool_destroy(subpool);
1720 return SVN_NO_ERROR;
1724 struct nls_receiver_baton
1726 int count;
1727 svn_location_segment_t *expected_segments;
1731 static const char *
1732 format_segment(svn_location_segment_t *segment,
1733 apr_pool_t *pool)
1735 return apr_psprintf(pool, "[r%ld-r%ld: /%s]",
1736 segment->range_start,
1737 segment->range_end,
1738 segment->path ? segment->path : "(null)");
1742 static svn_error_t *
1743 nls_receiver(svn_location_segment_t *segment,
1744 void *baton,
1745 apr_pool_t *pool)
1747 struct nls_receiver_baton *b = baton;
1748 svn_location_segment_t *expected_segment = b->expected_segments + b->count;
1750 /* expected_segments->range_end can't be 0, so if we see that, it's
1751 our end-of-the-list sentry. */
1752 if (! expected_segment->range_end)
1753 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
1754 "Got unexpected location segment: %s",
1755 format_segment(segment, pool));
1757 if (expected_segment->range_start != segment->range_start)
1758 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
1759 "Location segments differ\n"
1760 " Expected location segment: %s\n"
1761 " Actual location segment: %s",
1762 format_segment(expected_segment, pool),
1763 format_segment(segment, pool));
1764 b->count++;
1765 return SVN_NO_ERROR;
1769 static svn_error_t *
1770 check_location_segments(svn_repos_t *repos,
1771 const char *path,
1772 svn_revnum_t peg_rev,
1773 svn_revnum_t start_rev,
1774 svn_revnum_t end_rev,
1775 svn_location_segment_t *expected_segments,
1776 apr_pool_t *pool)
1778 struct nls_receiver_baton b;
1779 svn_location_segment_t *segment;
1781 /* Run svn_repos_node_location_segments() with a receiver that
1782 validates against EXPECTED_SEGMENTS. */
1783 b.count = 0;
1784 b.expected_segments = expected_segments;
1785 SVN_ERR(svn_repos_node_location_segments(repos, path, peg_rev,
1786 start_rev, end_rev, nls_receiver,
1787 &b, NULL, NULL, pool));
1789 /* Make sure we saw all of our expected segments. (If the
1790 'range_end' member of our expected_segments is 0, it's our
1791 end-of-the-list sentry. Otherwise, it's some segment we expect
1792 to see.) If not, raise an error. */
1793 segment = expected_segments + b.count;
1794 if (segment->range_end)
1795 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
1796 "Failed to get expected location segment: %s",
1797 format_segment(segment, pool));
1798 return SVN_NO_ERROR;
1802 static svn_error_t *
1803 node_location_segments(const char **msg,
1804 svn_boolean_t msg_only,
1805 svn_test_opts_t *opts,
1806 apr_pool_t *pool)
1808 apr_pool_t *subpool = svn_pool_create(pool);
1809 svn_repos_t *repos;
1810 svn_fs_t *fs;
1811 svn_fs_txn_t *txn;
1812 svn_fs_root_t *txn_root, *root;
1813 svn_revnum_t youngest_rev = 0;
1815 *msg = "test svn_repos_node_location_segments";
1816 if (msg_only)
1817 return SVN_NO_ERROR;
1819 /* Create the repository. */
1820 SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-location-segments",
1821 opts->fs_type, pool));
1822 fs = svn_repos_fs(repos);
1824 /* Revision 1: Create the Greek tree. */
1825 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
1826 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1827 SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
1828 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1829 svn_pool_clear(subpool);
1831 /* Revision 2: Modify A/D/H/chi and A/B/E/alpha. */
1832 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
1833 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1834 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "2", subpool));
1835 SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/alpha", "2", subpool));
1836 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1837 svn_pool_clear(subpool);
1839 /* Revision 3: Copy A/D to A/D2. */
1840 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
1841 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1842 SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool));
1843 SVN_ERR(svn_fs_copy(root, "A/D", txn_root, "A/D2", subpool));
1844 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1845 svn_pool_clear(subpool);
1847 /* Revision 4: Modify A/D/H/chi and A/D2/H/chi. */
1848 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
1849 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1850 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D/H/chi", "4", subpool));
1851 SVN_ERR(svn_test__set_file_contents(txn_root, "A/D2/H/chi", "4", subpool));
1852 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1853 svn_pool_clear(subpool);
1855 /* Revision 5: Delete A/D2/G. */
1856 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
1857 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1858 SVN_ERR(svn_fs_delete(txn_root, "A/D2/G", subpool));
1859 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1860 svn_pool_clear(subpool);
1862 /* Revision 6: Restore A/D2/G (from version 4). */
1863 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
1864 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1865 SVN_ERR(svn_fs_revision_root(&root, fs, 4, subpool));
1866 SVN_ERR(svn_fs_copy(root, "A/D2/G", txn_root, "A/D2/G", subpool));
1867 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1868 svn_pool_clear(subpool);
1870 /* Revision 7: Move A/D2 to A/D (replacing it). */
1871 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
1872 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
1873 SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, subpool));
1874 SVN_ERR(svn_fs_delete(txn_root, "A/D", subpool));
1875 SVN_ERR(svn_fs_copy(root, "A/D2", txn_root, "A/D", subpool));
1876 SVN_ERR(svn_fs_delete(txn_root, "A/D2", subpool));
1877 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
1878 svn_pool_clear(subpool);
1880 /* Check locations for /@HEAD. */
1882 svn_location_segment_t expected_segments[] =
1884 { 0, 7, "" },
1885 { 0 }
1887 SVN_ERR(check_location_segments(repos, "",
1888 SVN_INVALID_REVNUM,
1889 SVN_INVALID_REVNUM,
1890 SVN_INVALID_REVNUM,
1891 expected_segments, pool));
1894 /* Check locations for A/D@HEAD. */
1896 svn_location_segment_t expected_segments[] =
1898 { 7, 7, "A/D" },
1899 { 3, 6, "A/D2" },
1900 { 1, 2, "A/D" },
1901 { 0 }
1903 SVN_ERR(check_location_segments(repos, "A/D",
1904 SVN_INVALID_REVNUM,
1905 SVN_INVALID_REVNUM,
1906 SVN_INVALID_REVNUM,
1907 expected_segments, pool));
1910 /* Check a subset of the locations for A/D@HEAD. */
1912 svn_location_segment_t expected_segments[] =
1914 { 3, 5, "A/D2" },
1915 { 2, 2, "A/D" },
1916 { 0 }
1918 SVN_ERR(check_location_segments(repos, "A/D",
1919 SVN_INVALID_REVNUM,
1922 expected_segments, pool));
1925 /* Check a subset of locations for A/D2@5. */
1927 svn_location_segment_t expected_segments[] =
1929 { 3, 3, "A/D2" },
1930 { 2, 2, "A/D" },
1931 { 0 }
1933 SVN_ERR(check_location_segments(repos, "A/D2",
1937 expected_segments, pool));
1940 /* Check locations for A/D@6. */
1942 svn_location_segment_t expected_segments[] =
1944 { 1, 6, "A/D" },
1945 { 0 }
1947 SVN_ERR(check_location_segments(repos, "A/D",
1950 SVN_INVALID_REVNUM,
1951 expected_segments, pool));
1954 /* Check locations for A/D/G@HEAD. */
1956 svn_location_segment_t expected_segments[] =
1958 { 7, 7, "A/D/G" },
1959 { 6, 6, "A/D2/G" },
1960 { 5, 5, NULL },
1961 { 3, 4, "A/D2/G" },
1962 { 1, 2, "A/D2/G" },
1963 { 0 }
1965 SVN_ERR(check_location_segments(repos, "A/D/G",
1966 SVN_INVALID_REVNUM,
1967 SVN_INVALID_REVNUM,
1968 SVN_INVALID_REVNUM,
1969 expected_segments, pool));
1972 /* Check a subset of the locations for A/D/G@HEAD. */
1974 svn_location_segment_t expected_segments[] =
1976 { 3, 3, "A/D2/G" },
1977 { 2, 2, "A/D2/G" },
1978 { 0 }
1980 SVN_ERR(check_location_segments(repos, "A/D/G",
1981 SVN_INVALID_REVNUM,
1984 expected_segments, pool));
1987 return SVN_NO_ERROR;
1991 /* Test that the reporter doesn't send deltas under excluded paths. */
1992 static svn_error_t *
1993 reporter_depth_exclude(const char **msg,
1994 svn_boolean_t msg_only,
1995 svn_test_opts_t *opts,
1996 apr_pool_t *pool)
1998 svn_repos_t *repos;
1999 svn_fs_t *fs;
2000 svn_fs_txn_t *txn;
2001 svn_fs_root_t *txn_root;
2002 apr_pool_t *subpool = svn_pool_create(pool);
2003 svn_revnum_t youngest_rev;
2004 const svn_delta_editor_t *editor;
2005 void *edit_baton, *report_baton;
2006 svn_error_t *err;
2008 *msg = "test reporter and svn_depth_exclude";
2010 if (msg_only)
2011 return SVN_NO_ERROR;
2013 SVN_ERR(svn_test__create_repos(&repos, "test-repo-reporter-depth-exclude",
2014 opts->fs_type, pool));
2015 fs = svn_repos_fs(repos);
2017 /* Prepare a txn to receive the greek tree. */
2018 SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
2019 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
2020 SVN_ERR(svn_test__create_greek_tree(txn_root, subpool));
2021 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
2022 svn_pool_clear(subpool);
2024 /* Revision 2: make a bunch of changes */
2025 SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
2026 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
2028 static svn_test__txn_script_command_t script_entries[] = {
2029 { 'e', "iota", "Changed file 'iota'.\n" },
2030 { 'e', "A/D/G/pi", "Changed file 'pi'.\n" },
2031 { 'e', "A/mu", "Changed file 'mu'.\n" },
2032 { 'a', "A/D/foo", "New file 'foo'.\n" },
2033 { 'a', "A/B/bar", "New file 'bar'.\n" },
2034 { 'd', "A/D/H", NULL },
2035 { 'd', "A/B/E/beta", NULL }
2037 SVN_ERR(svn_test__txn_script_exec(txn_root,
2038 script_entries,
2039 sizeof(script_entries)/
2040 sizeof(script_entries[0]),
2041 subpool));
2043 SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool));
2044 svn_pool_clear(subpool);
2046 /* Confirm the contents of r2. */
2048 svn_fs_root_t *revision_root;
2049 static svn_test__tree_entry_t entries[] = {
2050 { "iota", "Changed file 'iota'.\n" },
2051 { "A", 0 },
2052 { "A/mu", "Changed file 'mu'.\n" },
2053 { "A/B", 0 },
2054 { "A/B/bar", "New file 'bar'.\n" },
2055 { "A/B/lambda", "This is the file 'lambda'.\n" },
2056 { "A/B/E", 0 },
2057 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
2058 { "A/B/F", 0 },
2059 { "A/C", 0 },
2060 { "A/D", 0 },
2061 { "A/D/foo", "New file 'foo'.\n" },
2062 { "A/D/gamma", "This is the file 'gamma'.\n" },
2063 { "A/D/G", 0 },
2064 { "A/D/G/pi", "Changed file 'pi'.\n" },
2065 { "A/D/G/rho", "This is the file 'rho'.\n" },
2066 { "A/D/G/tau", "This is the file 'tau'.\n" },
2068 SVN_ERR(svn_fs_revision_root(&revision_root, fs,
2069 youngest_rev, subpool));
2070 SVN_ERR(svn_test__validate_tree(revision_root,
2071 entries,
2072 sizeof(entries)/sizeof(entries[0]),
2073 subpool));
2076 /* Run an update from r1 to r2, excluding iota and everything under
2077 A/D. Record the editor commands in a temporary txn. */
2078 SVN_ERR(svn_fs_begin_txn(&txn, fs, 1, subpool));
2079 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
2080 SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs,
2081 txn_root, "", subpool));
2083 SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL,
2084 TRUE, svn_depth_infinity, FALSE, FALSE,
2085 editor, edit_baton, NULL, NULL, subpool));
2086 SVN_ERR(svn_repos_set_path3(report_baton, "", 1,
2087 svn_depth_infinity,
2088 FALSE, NULL, subpool));
2089 SVN_ERR(svn_repos_set_path3(report_baton, "iota", SVN_INVALID_REVNUM,
2090 svn_depth_exclude,
2091 FALSE, NULL, subpool));
2092 SVN_ERR(svn_repos_set_path3(report_baton, "A/D", SVN_INVALID_REVNUM,
2093 svn_depth_exclude,
2094 FALSE, NULL, subpool));
2095 SVN_ERR(svn_repos_finish_report(report_baton, subpool));
2097 /* Confirm the contents of the txn. */
2098 /* This should have iota and A/D from r1, and everything else from
2099 r2. */
2101 static svn_test__tree_entry_t entries[] = {
2102 { "iota", "This is the file 'iota'.\n" },
2103 { "A", 0 },
2104 { "A/mu", "Changed file 'mu'.\n" },
2105 { "A/B", 0 },
2106 { "A/B/bar", "New file 'bar'.\n" },
2107 { "A/B/lambda", "This is the file 'lambda'.\n" },
2108 { "A/B/E", 0 },
2109 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
2110 { "A/B/F", 0 },
2111 { "A/C", 0 },
2112 { "A/D", 0 },
2113 { "A/D/gamma", "This is the file 'gamma'.\n" },
2114 { "A/D/G", 0 },
2115 { "A/D/G/pi", "This is the file 'pi'.\n" },
2116 { "A/D/G/rho", "This is the file 'rho'.\n" },
2117 { "A/D/G/tau", "This is the file 'tau'.\n" },
2118 { "A/D/H", 0 },
2119 { "A/D/H/chi", "This is the file 'chi'.\n" },
2120 { "A/D/H/psi", "This is the file 'psi'.\n" },
2121 { "A/D/H/omega", "This is the file 'omega'.\n" }
2123 SVN_ERR(svn_test__validate_tree(txn_root,
2124 entries,
2125 sizeof(entries)/sizeof(entries[0]),
2126 subpool));
2127 svn_pool_clear(subpool);
2130 /* Clean up after ourselves. */
2131 svn_error_clear(svn_fs_abort_txn(txn, subpool));
2133 /* Expect an error on an illegal report for r1 to r2. The illegal
2134 sequence is that we exclude A/D, then set_path() below A/D. */
2135 SVN_ERR(svn_fs_begin_txn(&txn, fs, 1, subpool));
2136 SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
2137 SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs,
2138 txn_root, "", subpool));
2140 SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL,
2141 TRUE, svn_depth_infinity, FALSE, FALSE,
2142 editor, edit_baton, NULL, NULL, subpool));
2143 SVN_ERR(svn_repos_set_path3(report_baton, "", 1,
2144 svn_depth_infinity,
2145 FALSE, NULL, subpool));
2146 SVN_ERR(svn_repos_set_path3(report_baton, "iota", SVN_INVALID_REVNUM,
2147 svn_depth_exclude,
2148 FALSE, NULL, subpool));
2149 SVN_ERR(svn_repos_set_path3(report_baton, "A/D", SVN_INVALID_REVNUM,
2150 svn_depth_exclude,
2151 FALSE, NULL, subpool));
2153 /* This is the illegal call, since A/D was excluded above; the call
2154 itself will not error, but finish_report() will. As of r28098,
2155 this delayed error behavior is not actually promised by the
2156 reporter API, which merely warns callers not to touch a path
2157 underneath a previously excluded path without defining what will
2158 happen if they do. However, it's still useful to test for the
2159 error, since the reporter code is sensitive and we'd certainly
2160 want to know about it if the behavior were to change. */
2161 SVN_ERR(svn_repos_set_path3(report_baton, "A/D/G/pi",
2162 SVN_INVALID_REVNUM,
2163 svn_depth_infinity,
2164 FALSE, NULL, subpool));
2165 err = svn_repos_finish_report(report_baton, subpool);
2166 if (! err)
2168 return svn_error_createf
2169 (SVN_ERR_TEST_FAILED, NULL,
2170 "Illegal report of \"A/D/G/pi\" did not error as expected");
2172 else if (err->apr_err != SVN_ERR_FS_NOT_FOUND)
2174 return svn_error_createf
2175 (SVN_ERR_TEST_FAILED, err,
2176 "Illegal report of \"A/D/G/pi\" got wrong kind of error:");
2179 /* Clean up after ourselves. */
2180 svn_error_clear(err);
2181 svn_error_clear(svn_fs_abort_txn(txn, subpool));
2183 svn_pool_destroy(subpool);
2185 return SVN_NO_ERROR;
2190 /* The test table. */
2192 struct svn_test_descriptor_t test_funcs[] =
2194 SVN_TEST_NULL,
2195 SVN_TEST_PASS(dir_deltas),
2196 SVN_TEST_PASS(node_tree_delete_under_copy),
2197 SVN_TEST_PASS(revisions_changed),
2198 SVN_TEST_PASS(node_locations),
2199 SVN_TEST_PASS(node_locations2),
2200 SVN_TEST_PASS(rmlocks),
2201 SVN_TEST_PASS(authz),
2202 SVN_TEST_PASS(commit_editor_authz),
2203 SVN_TEST_PASS(commit_continue_txn),
2204 SVN_TEST_PASS(node_location_segments),
2205 SVN_TEST_PASS(reporter_depth_exclude),
2206 SVN_TEST_NULL