source: valtobtest/subversion-1.6.2/subversion/tests/libsvn_repos/repos-test.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 92.6 KB
Line 
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
48static svn_error_t *
49dir_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
368static svn_error_t *
369node_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(). */
455static const char *
456print_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. */
492static svn_error_t *
493history_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
503struct revisions_changed_results
504{
505  const char *path;
506  int num_revs;
507  svn_revnum_t revs_changed[11];
508};
509
510
511static svn_error_t *
512revisions_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
720struct 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. */
727static svn_error_t *
728check_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 */
752static svn_error_t *
753check_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
771static svn_error_t *
772node_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
824static svn_error_t *
825node_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. */
897typedef struct rmlocks_baton_t {
898  apr_hash_t *removed;
899  apr_pool_t *pool;
900} rmlocks_baton_t;
901
902/* The file baton. */
903typedef 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. */
909static svn_error_t *
910rmlocks_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. */
928static svn_error_t *
929rmlocks_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. */
958static svn_error_t *
959rmlocks_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. */
969static svn_error_t *
970rmlocks_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. */
983static svn_error_t *
984create_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. */
1010static svn_error_t *
1011rmlocks_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. */
1031static svn_error_t *
1032rmlocks(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. */
1124static svn_error_t *
1125authz_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. */
1150static svn_error_t *
1151authz(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. */
1346static svn_error_t *
1347commit_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 */
1365static svn_error_t *
1366commit_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. */
1586static svn_error_t *
1587dummy_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. */
1594static svn_error_t *
1595commit_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
1710struct nls_receiver_baton
1711{
1712  int count;
1713  svn_location_segment_t *expected_segments;
1714};
1715
1716
1717static const char *
1718format_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
1728static svn_error_t *
1729nls_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
1755static svn_error_t *
1756check_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
1788static svn_error_t *
1789node_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. */
1983static svn_error_t *
1984reporter_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 */
2199static svn_error_t *
2200prop_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 */
2260static svn_error_t *
2261prop_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. */
2333static svn_error_t *
2334log_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
2344static svn_error_t *
2345get_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
2431struct 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  };
Note: See TracBrowser for help on using the repository browser.