~svn/ubuntu/oneiric/subversion/ppa

« back to all changes in this revision

Viewing changes to subversion/tests/libsvn_repos/repos-test.c

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* repos-test.c --- tests for the filesystem
 
2
 *
 
3
 * ====================================================================
 
4
 * Copyright (c) 2000-2004 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
#include <apr_md5.h>
 
22
#include "svn_pools.h"
 
23
#include "svn_error.h"
 
24
#include "svn_fs.h"
 
25
#include "svn_repos.h"
 
26
#include "svn_path.h"
 
27
#include "svn_delta.h"
 
28
#include "svn_test.h"
 
29
#include "../fs-helpers.h"
 
30
 
 
31
#include "dir-delta-editor.h"
 
32
 
 
33
 
 
34
 
 
35
 
 
36
static svn_error_t *
 
37
dir_deltas (const char **msg,
 
38
            svn_boolean_t msg_only,
 
39
            svn_test_opts_t *opts,
 
40
            apr_pool_t *pool)
 
41
 
42
  svn_repos_t *repos;
 
43
  svn_fs_t *fs;
 
44
  svn_fs_txn_t *txn;
 
45
  svn_fs_root_t *txn_root, *revision_root;
 
46
  svn_revnum_t youngest_rev;
 
47
  void *edit_baton;
 
48
  const svn_delta_editor_t *editor;
 
49
  svn_test__tree_t expected_trees[8];
 
50
  int revision_count = 0;
 
51
  int i, j;
 
52
  apr_pool_t *subpool = svn_pool_create (pool);
 
53
 
 
54
  *msg = "test svn_repos_dir_delta";
 
55
 
 
56
  if (msg_only)
 
57
    return SVN_NO_ERROR;
 
58
 
 
59
  /* The Test Plan
 
60
     
 
61
     The filesystem function svn_repos_dir_delta exists to drive an
 
62
     editor in such a way that given a source tree S and a target tree
 
63
     T, that editor manipulation will transform S into T, insomuch as
 
64
     directories and files, and their contents and properties, go.
 
65
     The general notion of the test plan will be to create pairs of
 
66
     trees (S, T), and an editor that edits a copy of tree S, run them
 
67
     through svn_repos_dir_delta, and then verify that the edited copy of
 
68
     S is identical to T when it is all said and done.  */
 
69
 
 
70
  /* Create a filesystem and repository. */
 
71
  SVN_ERR (svn_test__create_repos (&repos, "test-repo-dir-deltas", 
 
72
                                   opts->fs_type, pool));
 
73
  fs = svn_repos_fs (repos);
 
74
  expected_trees[revision_count].num_entries = 0;
 
75
  expected_trees[revision_count++].entries = 0;
 
76
 
 
77
  /* Prepare a txn to receive the greek tree. */
 
78
  SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, subpool));
 
79
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
80
  SVN_ERR (svn_test__create_greek_tree (txn_root, subpool));
 
81
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, subpool));
 
82
 
 
83
  /***********************************************************************/
 
84
  /* REVISION 1 */
 
85
  /***********************************************************************/
 
86
  {
 
87
    static svn_test__tree_entry_t expected_entries[] = {
 
88
      /* path, contents (0 = dir) */
 
89
      { "iota",        "This is the file 'iota'.\n" },
 
90
      { "A",           0 },
 
91
      { "A/mu",        "This is the file 'mu'.\n" },
 
92
      { "A/B",         0 },
 
93
      { "A/B/lambda",  "This is the file 'lambda'.\n" },
 
94
      { "A/B/E",       0 },
 
95
      { "A/B/E/alpha", "This is the file 'alpha'.\n" },
 
96
      { "A/B/E/beta",  "This is the file 'beta'.\n" },
 
97
      { "A/B/F",       0 },
 
98
      { "A/C",         0 },
 
99
      { "A/D",         0 },
 
100
      { "A/D/gamma",   "This is the file 'gamma'.\n" },
 
101
      { "A/D/G",       0 },
 
102
      { "A/D/G/pi",    "This is the file 'pi'.\n" },
 
103
      { "A/D/G/rho",   "This is the file 'rho'.\n" },
 
104
      { "A/D/G/tau",   "This is the file 'tau'.\n" },
 
105
      { "A/D/H",       0 },
 
106
      { "A/D/H/chi",   "This is the file 'chi'.\n" },
 
107
      { "A/D/H/psi",   "This is the file 'psi'.\n" },
 
108
      { "A/D/H/omega", "This is the file 'omega'.\n" }
 
109
    };
 
110
    expected_trees[revision_count].entries = expected_entries;
 
111
    expected_trees[revision_count].num_entries = 20;
 
112
    SVN_ERR (svn_fs_revision_root (&revision_root, fs, 
 
113
                                   youngest_rev, subpool)); 
 
114
    SVN_ERR (svn_test__validate_tree 
 
115
             (revision_root, expected_trees[revision_count].entries,
 
116
              expected_trees[revision_count].num_entries, subpool));
 
117
    revision_count++;
 
118
  }
 
119
  svn_pool_clear (subpool);
 
120
 
 
121
  /* Make a new txn based on the youngest revision, make some changes,
 
122
     and commit those changes (which makes a new youngest
 
123
     revision). */
 
124
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool));
 
125
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
126
  {
 
127
    static svn_test__txn_script_command_t script_entries[] = {
 
128
      { 'a', "A/delta",     "This is the file 'delta'.\n" },
 
129
      { 'a', "A/epsilon",   "This is the file 'epsilon'.\n" },
 
130
      { 'a', "A/B/Z",       0 },
 
131
      { 'a', "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
 
132
      { 'd', "A/C",         0 },
 
133
      { 'd', "A/mu",        "" },
 
134
      { 'd', "A/D/G/tau",   "" },
 
135
      { 'd', "A/D/H/omega", "" },
 
136
      { 'e', "iota",        "Changed file 'iota'.\n" },
 
137
      { 'e', "A/D/G/rho",   "Changed file 'rho'.\n" }
 
138
    };
 
139
    SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 10, 
 
140
                                        subpool));
 
141
  }
 
142
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, subpool));
 
143
 
 
144
  /***********************************************************************/
 
145
  /* REVISION 2 */
 
146
  /***********************************************************************/
 
147
  {
 
148
    static svn_test__tree_entry_t expected_entries[] = {
 
149
      /* path, contents (0 = dir) */
 
150
      { "iota",        "Changed file 'iota'.\n" },
 
151
      { "A",           0 },
 
152
      { "A/delta",     "This is the file 'delta'.\n" },
 
153
      { "A/epsilon",   "This is the file 'epsilon'.\n" },
 
154
      { "A/B",         0 },
 
155
      { "A/B/lambda",  "This is the file 'lambda'.\n" },
 
156
      { "A/B/E",       0 },
 
157
      { "A/B/E/alpha", "This is the file 'alpha'.\n" },
 
158
      { "A/B/E/beta",  "This is the file 'beta'.\n" },
 
159
      { "A/B/F",       0 },
 
160
      { "A/B/Z",       0 },
 
161
      { "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
 
162
      { "A/D",         0 },
 
163
      { "A/D/gamma",   "This is the file 'gamma'.\n" },
 
164
      { "A/D/G",       0 },
 
165
      { "A/D/G/pi",    "This is the file 'pi'.\n" },
 
166
      { "A/D/G/rho",   "Changed file 'rho'.\n" },
 
167
      { "A/D/H",       0 },
 
168
      { "A/D/H/chi",   "This is the file 'chi'.\n" },
 
169
      { "A/D/H/psi",   "This is the file 'psi'.\n" }
 
170
    };
 
171
    expected_trees[revision_count].entries = expected_entries;
 
172
    expected_trees[revision_count].num_entries = 20;
 
173
    SVN_ERR (svn_fs_revision_root (&revision_root, fs, 
 
174
                                   youngest_rev, subpool)); 
 
175
    SVN_ERR (svn_test__validate_tree 
 
176
             (revision_root, expected_trees[revision_count].entries,
 
177
              expected_trees[revision_count].num_entries, subpool));
 
178
    revision_count++;
 
179
  } 
 
180
  svn_pool_clear (subpool);
 
181
 
 
182
  /* Make a new txn based on the youngest revision, make some changes,
 
183
     and commit those changes (which makes a new youngest
 
184
     revision). */
 
185
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool));
 
186
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
187
  {
 
188
    static svn_test__txn_script_command_t script_entries[] = {
 
189
      { 'a', "A/mu",        "Re-added file 'mu'.\n" },
 
190
      { 'a', "A/D/H/omega", 0 }, /* re-add omega as directory! */
 
191
      { 'd', "iota",        "" },
 
192
      { 'e', "A/delta",     "This is the file 'delta'.\nLine 2.\n" }
 
193
    };
 
194
    SVN_ERR (svn_test__txn_script_exec (txn_root, script_entries, 4, subpool));
 
195
  }
 
196
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, subpool));
 
197
 
 
198
  /***********************************************************************/
 
199
  /* REVISION 3 */
 
200
  /***********************************************************************/
 
201
  {
 
202
    static svn_test__tree_entry_t expected_entries[] = {
 
203
      /* path, contents (0 = dir) */
 
204
      { "A",           0 },
 
205
      { "A/delta",     "This is the file 'delta'.\nLine 2.\n" },
 
206
      { "A/epsilon",   "This is the file 'epsilon'.\n" },
 
207
      { "A/mu",        "Re-added file 'mu'.\n" },
 
208
      { "A/B",         0 },
 
209
      { "A/B/lambda",  "This is the file 'lambda'.\n" },
 
210
      { "A/B/E",       0 },
 
211
      { "A/B/E/alpha", "This is the file 'alpha'.\n" },
 
212
      { "A/B/E/beta",  "This is the file 'beta'.\n" },
 
213
      { "A/B/F",       0 },
 
214
      { "A/B/Z",       0 },
 
215
      { "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
 
216
      { "A/D",         0 },
 
217
      { "A/D/gamma",   "This is the file 'gamma'.\n" },
 
218
      { "A/D/G",       0 },
 
219
      { "A/D/G/pi",    "This is the file 'pi'.\n" },
 
220
      { "A/D/G/rho",   "Changed file 'rho'.\n" },
 
221
      { "A/D/H",       0 },
 
222
      { "A/D/H/chi",   "This is the file 'chi'.\n" },
 
223
      { "A/D/H/psi",   "This is the file 'psi'.\n" },
 
224
      { "A/D/H/omega", 0 }
 
225
    };
 
226
    expected_trees[revision_count].entries = expected_entries;
 
227
    expected_trees[revision_count].num_entries = 21;
 
228
    SVN_ERR (svn_fs_revision_root (&revision_root, fs, 
 
229
                                   youngest_rev, subpool)); 
 
230
    SVN_ERR (svn_test__validate_tree 
 
231
             (revision_root, expected_trees[revision_count].entries,
 
232
              expected_trees[revision_count].num_entries, subpool));
 
233
    revision_count++;
 
234
  }
 
235
  svn_pool_clear (subpool);
 
236
 
 
237
  /* Make a new txn based on the youngest revision, make some changes,
 
238
     and commit those changes (which makes a new youngest
 
239
     revision). */
 
240
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, subpool));
 
241
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
242
  SVN_ERR (svn_fs_revision_root (&revision_root, fs, youngest_rev, subpool)); 
 
243
  SVN_ERR (svn_fs_copy (revision_root, "A/D/G",
 
244
                        txn_root, "A/D/G2",
 
245
                        subpool));
 
246
  SVN_ERR (svn_fs_copy (revision_root, "A/epsilon",
 
247
                        txn_root, "A/B/epsilon",
 
248
                        subpool));
 
249
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, subpool));
 
250
 
 
251
  /***********************************************************************/
 
252
  /* REVISION 4 */
 
253
  /***********************************************************************/
 
254
  {
 
255
    static svn_test__tree_entry_t expected_entries[] = {
 
256
      /* path, contents (0 = dir) */
 
257
      { "A",           0 },
 
258
      { "A/delta",     "This is the file 'delta'.\nLine 2.\n" },
 
259
      { "A/epsilon",   "This is the file 'epsilon'.\n" },
 
260
      { "A/mu",        "Re-added file 'mu'.\n" },
 
261
      { "A/B",         0 },
 
262
      { "A/B/epsilon", "This is the file 'epsilon'.\n" },
 
263
      { "A/B/lambda",  "This is the file 'lambda'.\n" },
 
264
      { "A/B/E",       0 },
 
265
      { "A/B/E/alpha", "This is the file 'alpha'.\n" },
 
266
      { "A/B/E/beta",  "This is the file 'beta'.\n" },
 
267
      { "A/B/F",       0 },
 
268
      { "A/B/Z",       0 },
 
269
      { "A/B/Z/zeta",  "This is the file 'zeta'.\n" },
 
270
      { "A/D",         0 },
 
271
      { "A/D/gamma",   "This is the file 'gamma'.\n" },
 
272
      { "A/D/G",       0 },
 
273
      { "A/D/G/pi",    "This is the file 'pi'.\n" },
 
274
      { "A/D/G/rho",   "Changed file 'rho'.\n" },
 
275
      { "A/D/G2",      0 },
 
276
      { "A/D/G2/pi",   "This is the file 'pi'.\n" },
 
277
      { "A/D/G2/rho",  "Changed file 'rho'.\n" },
 
278
      { "A/D/H",       0 },
 
279
      { "A/D/H/chi",   "This is the file 'chi'.\n" },
 
280
      { "A/D/H/psi",   "This is the file 'psi'.\n" },
 
281
      { "A/D/H/omega", 0 }
 
282
    };
 
283
    expected_trees[revision_count].entries = expected_entries;
 
284
    expected_trees[revision_count].num_entries = 25;
 
285
    SVN_ERR (svn_fs_revision_root (&revision_root, fs, 
 
286
                                   youngest_rev, pool)); 
 
287
    SVN_ERR (svn_test__validate_tree 
 
288
             (revision_root, expected_trees[revision_count].entries,
 
289
              expected_trees[revision_count].num_entries, subpool));
 
290
    revision_count++;
 
291
  }
 
292
  svn_pool_clear (subpool);
 
293
 
 
294
  /* THE BIG IDEA: Now that we have a collection of revisions, let's
 
295
     first make sure that given any two revisions, we can get the
 
296
     right delta between them.  We'll do this by selecting our two
 
297
     revisions, R1 and R2, basing a transaction off R1, deltafying the
 
298
     txn with respect to R2, and then making sure our final txn looks
 
299
     exactly like R2.  This should work regardless of the
 
300
     chronological order in which R1 and R2 were created.  */
 
301
  for (i = 0; i < revision_count; i++)
 
302
    {
 
303
      for (j = 0; j < revision_count; j++)
 
304
        {
 
305
          /* Prepare a txn that will receive the changes from
 
306
             svn_repos_dir_delta */
 
307
          SVN_ERR (svn_fs_begin_txn (&txn, fs, i, subpool));
 
308
          SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
309
 
 
310
          /* Get the editor that will be modifying our transaction. */
 
311
          SVN_ERR (dir_delta_get_editor (&editor,
 
312
                                         &edit_baton,
 
313
                                         fs,
 
314
                                         txn_root,
 
315
                                         "",
 
316
                                         subpool));
 
317
 
 
318
          /* Here's the kicker...do the directory delta. */
 
319
          SVN_ERR (svn_fs_revision_root (&revision_root, fs, j, subpool)); 
 
320
          SVN_ERR (svn_repos_dir_delta (txn_root,
 
321
                                        "",
 
322
                                        "",
 
323
                                        revision_root,
 
324
                                        "",
 
325
                                        editor,
 
326
                                        edit_baton,
 
327
                                        NULL,
 
328
                                        NULL,
 
329
                                        TRUE,
 
330
                                        TRUE,
 
331
                                        FALSE,
 
332
                                        FALSE,
 
333
                                        subpool));
 
334
 
 
335
          /* Hopefully at this point our transaction has been modified
 
336
             to look exactly like our latest revision.  We'll check
 
337
             that. */
 
338
          SVN_ERR (svn_test__validate_tree 
 
339
                   (txn_root, expected_trees[j].entries,
 
340
                    expected_trees[j].num_entries, subpool));
 
341
 
 
342
          /* We don't really want to do anything with this
 
343
             transaction...so we'll abort it (good for software, bad
 
344
             bad bad for society). */
 
345
          svn_error_clear (svn_fs_abort_txn (txn, subpool));
 
346
          svn_pool_clear (subpool);
 
347
        }
 
348
    }
 
349
 
 
350
  svn_pool_destroy (subpool);
 
351
 
 
352
  return SVN_NO_ERROR;
 
353
}
 
354
 
 
355
 
 
356
static svn_error_t *
 
357
node_tree_delete_under_copy (const char **msg,
 
358
                             svn_boolean_t msg_only,
 
359
                             svn_test_opts_t *opts,
 
360
                             apr_pool_t *pool)
 
361
 
362
  svn_repos_t *repos;
 
363
  svn_fs_t *fs;
 
364
  svn_fs_txn_t *txn;
 
365
  svn_fs_root_t *txn_root, *revision_root, *revision_2_root;
 
366
  svn_revnum_t youngest_rev;
 
367
  void *edit_baton;
 
368
  const svn_delta_editor_t *editor; 
 
369
  svn_repos_node_t *tree;
 
370
  apr_pool_t *subpool = svn_pool_create (pool);
 
371
 
 
372
  *msg = "test deletions under copies in node_tree code";
 
373
 
 
374
  if (msg_only)
 
375
    return SVN_NO_ERROR;
 
376
 
 
377
  /* Create a filesystem and repository. */
 
378
  SVN_ERR (svn_test__create_repos (&repos, "test-repo-del-under-copy", 
 
379
                                   opts->fs_type, pool));
 
380
  fs = svn_repos_fs (repos);
 
381
 
 
382
  /* Prepare a txn to receive the greek tree. */
 
383
  SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, pool));
 
384
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool));
 
385
 
 
386
  /* Create and commit the greek tree. */
 
387
  SVN_ERR (svn_test__create_greek_tree (txn_root, pool));
 
388
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, pool));
 
389
 
 
390
  /* Now, commit again, this time after copying a directory, and then
 
391
     deleting some paths under that directory. */
 
392
  SVN_ERR (svn_fs_revision_root (&revision_root, fs, youngest_rev, pool)); 
 
393
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool));
 
394
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool));
 
395
  SVN_ERR (svn_fs_copy (revision_root, "A", txn_root, "Z", pool));
 
396
  SVN_ERR (svn_fs_delete (txn_root, "Z/D/G/rho", pool));
 
397
  SVN_ERR (svn_fs_delete (txn_root, "Z/D/H", pool));
 
398
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, pool));
 
399
 
 
400
  /* Now, we run the node_tree editor code, and see that a) it doesn't
 
401
     bomb out, and b) that our nodes are all good. */
 
402
  SVN_ERR (svn_fs_revision_root (&revision_2_root, fs, youngest_rev, pool)); 
 
403
  SVN_ERR (svn_repos_node_editor (&editor, &edit_baton, repos,
 
404
                                  revision_root, revision_2_root, 
 
405
                                  pool, subpool));
 
406
  SVN_ERR (svn_repos_replay (revision_2_root, editor, edit_baton, subpool));
 
407
  
 
408
  /* Get the root of the generated tree, and cleanup our mess. */
 
409
  tree = svn_repos_node_from_baton (edit_baton);
 
410
  svn_pool_destroy (subpool);
 
411
 
 
412
  /* See that we got what we expected (fortunately, svn_repos_replay
 
413
     drivers editor paths in a predictable fashion!). */
 
414
 
 
415
  if (! (tree /* / */
 
416
         && tree->child /* /Z */
 
417
         && tree->child->child /* /Z/D */
 
418
         && tree->child->child->child /* /Z/D/G */
 
419
         && tree->child->child->child->child /* /Z/D/G/rho */
 
420
         && tree->child->child->child->sibling)) /* /Z/D/H */
 
421
    return svn_error_create (SVN_ERR_TEST_FAILED, NULL, 
 
422
                             "Generated node tree is bogus.");
 
423
 
 
424
  if (! ((strcmp (tree->name, "") == 0)
 
425
         && (strcmp (tree->child->name, "Z") == 0)
 
426
         && (strcmp (tree->child->child->name, "D") == 0)
 
427
         && (strcmp (tree->child->child->child->name, "G") == 0)
 
428
         && ((strcmp (tree->child->child->child->child->name, "rho") == 0)
 
429
             && (tree->child->child->child->child->kind == svn_node_file)
 
430
             && (tree->child->child->child->child->action == 'D'))
 
431
         && ((strcmp (tree->child->child->child->sibling->name, "H") == 0)
 
432
             && (tree->child->child->child->sibling->kind == svn_node_dir)
 
433
             && (tree->child->child->child->sibling->action == 'D'))))
 
434
    return svn_error_create (SVN_ERR_TEST_FAILED, NULL, 
 
435
                             "Generated node tree is bogus.");
 
436
 
 
437
  return SVN_NO_ERROR;
 
438
}
 
439
 
 
440
 
 
441
/* Helper for revisions_changed(). */
 
442
static const char *
 
443
print_chrevs (const apr_array_header_t *revs_got,
 
444
              int num_revs_expected,
 
445
              const svn_revnum_t *revs_expected,
 
446
              apr_pool_t *pool)
 
447
{
 
448
  int i;
 
449
  const char *outstr;
 
450
  svn_revnum_t rev;
 
451
 
 
452
  outstr = apr_psprintf (pool, "Got: { ");
 
453
  if (revs_got)
 
454
    {
 
455
      for (i = 0; i < revs_got->nelts; i++)
 
456
        {
 
457
          rev = ((svn_revnum_t *)revs_got->elts)[i];
 
458
          outstr = apr_pstrcat (pool, 
 
459
                                outstr,
 
460
                                apr_psprintf (pool, "%ld ", rev),
 
461
                                NULL);
 
462
        }
 
463
    }
 
464
  outstr = apr_pstrcat (pool, outstr, "}  Expected: { ", NULL);
 
465
  for (i = 0; i < num_revs_expected; i++)
 
466
    {
 
467
      outstr = apr_pstrcat (pool, 
 
468
                            outstr,
 
469
                            apr_psprintf (pool, "%ld ",
 
470
                                          revs_expected[i]),
 
471
                            NULL);
 
472
    }
 
473
  return apr_pstrcat (pool, outstr, "}", NULL);
 
474
}
 
475
 
 
476
 
 
477
/* Implements svn_repos_history_func_t interface.  Accumulate history
 
478
   revisions the apr_array_header_t * which is the BATON. */
 
479
static svn_error_t *
 
480
history_to_revs_array (void *baton,
 
481
                       const char *path,
 
482
                       svn_revnum_t revision,
 
483
                       apr_pool_t *pool)
 
484
{
 
485
  apr_array_header_t *revs_array = baton;
 
486
  APR_ARRAY_PUSH (revs_array, svn_revnum_t) = revision;
 
487
  return SVN_NO_ERROR;
 
488
}
 
489
 
 
490
struct revisions_changed_results
 
491
{
 
492
  const char *path;
 
493
  int num_revs;
 
494
  svn_revnum_t revs_changed[11];
 
495
};
 
496
 
 
497
 
 
498
static svn_error_t *
 
499
revisions_changed (const char **msg,
 
500
                   svn_boolean_t msg_only,
 
501
                   svn_test_opts_t *opts,
 
502
                   apr_pool_t *pool)
 
503
 
504
  apr_pool_t *spool = svn_pool_create (pool);
 
505
  svn_repos_t *repos;
 
506
  svn_fs_t *fs;
 
507
  svn_fs_txn_t *txn;
 
508
  svn_fs_root_t *txn_root, *rev_root;
 
509
  svn_revnum_t youngest_rev = 0;
 
510
  
 
511
  *msg = "test svn_repos_history() (partially)";
 
512
 
 
513
  if (msg_only)
 
514
    return SVN_NO_ERROR;
 
515
 
 
516
  /* Create a filesystem and repository. */
 
517
  SVN_ERR (svn_test__create_repos (&repos, "test-repo-revisions-changed", 
 
518
                                   opts->fs_type, pool));
 
519
  fs = svn_repos_fs (repos);
 
520
 
 
521
  /*** Testing Algorithm ***
 
522
 
 
523
     1.  Create a greek tree in revision 1.
 
524
     2.  Make a series of new revisions, changing a file here and file
 
525
         there.
 
526
     3.  Loop over each path in each revision, verifying that we get
 
527
         the right revisions-changed array back from the filesystem.
 
528
  */
 
529
 
 
530
  /* Created the greek tree in revision 1. */
 
531
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
532
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
533
  SVN_ERR (svn_test__create_greek_tree (txn_root, spool));
 
534
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
535
  svn_pool_clear (spool);
 
536
 
 
537
  /* Revision 2 - mu, alpha, omega */
 
538
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
539
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
540
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/mu", "2", spool));
 
541
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/E/alpha", "2", spool));
 
542
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/omega", "2", spool));
 
543
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
544
  svn_pool_clear (spool);
 
545
 
 
546
  /* Revision 3 - iota, lambda, psi, omega */
 
547
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
548
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
549
  SVN_ERR (svn_test__set_file_contents (txn_root, "iota", "3", spool));
 
550
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/lambda", "3", spool));
 
551
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/psi", "3", spool));
 
552
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/omega", "3", spool));
 
553
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
554
  svn_pool_clear (spool);
 
555
 
 
556
  /* Revision 4 - iota, beta, gamma, pi, rho */
 
557
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
558
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
559
  SVN_ERR (svn_test__set_file_contents (txn_root, "iota", "4", spool));
 
560
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/E/beta", "4", spool));
 
561
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/gamma", "4", spool));
 
562
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/G/pi", "4", spool));
 
563
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/G/rho", "4", spool));
 
564
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
565
  svn_pool_clear (spool);
 
566
 
 
567
  /* Revision 5 - mu, alpha, tau, chi */
 
568
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
569
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
570
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/mu", "5", spool));
 
571
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/B/E/alpha", "5", spool));
 
572
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/G/tau", "5", spool));
 
573
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/H/chi", "5", spool));
 
574
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
575
  svn_pool_clear (spool);
 
576
 
 
577
  /* Revision 6 - move A/D to A/Z */
 
578
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
579
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
580
  SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool));
 
581
  SVN_ERR (svn_fs_copy (rev_root, "A/D", txn_root, "A/Z", spool));
 
582
  SVN_ERR (svn_fs_delete (txn_root, "A/D", spool));
 
583
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
584
  svn_pool_clear (spool);
 
585
 
 
586
  /* Revision 7 - edit A/Z/G/pi */
 
587
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
588
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
589
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/Z/G/pi", "7", spool));
 
590
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
591
  svn_pool_clear (spool);
 
592
 
 
593
  /* Revision 8 - move A/Z back to A/D, edit iota */
 
594
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
595
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
596
  SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool));
 
597
  SVN_ERR (svn_fs_copy (rev_root, "A/Z", txn_root, "A/D", spool));
 
598
  SVN_ERR (svn_fs_delete (txn_root, "A/Z", spool));
 
599
  SVN_ERR (svn_test__set_file_contents (txn_root, "iota", "8", spool));
 
600
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
601
  svn_pool_clear (spool);
 
602
 
 
603
  /* Revision 9 - copy A/D/G to A/D/Q */
 
604
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
605
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
606
  SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool));
 
607
  SVN_ERR (svn_fs_copy (rev_root, "A/D/G", txn_root, "A/D/Q", spool));
 
608
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
609
  svn_pool_clear (spool);
 
610
 
 
611
  /* Revision 10 - edit A/D/Q/pi and A/D/Q/rho */
 
612
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool));
 
613
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool));
 
614
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/Q/pi", "10", spool));
 
615
  SVN_ERR (svn_test__set_file_contents (txn_root, "A/D/Q/rho", "10", spool));
 
616
  SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn, spool));
 
617
  svn_pool_clear (spool);
 
618
 
 
619
  /* Now, it's time to verify our results. */
 
620
  {
 
621
    int j;
 
622
    /* Number, and list of, changed revisions for each path.  Note
 
623
       that for now, bubble-up in directories causes the directory to
 
624
       appear changed though no entries were added or removed, and no
 
625
       property mods occurred.  Also note that this matrix represents
 
626
       only the final state of the paths existing in HEAD of the
 
627
       repository.
 
628
 
 
629
       Notice for each revision, you can glance down that revision's
 
630
       column in this table and see all the paths modified directory or
 
631
       via bubble-up. */
 
632
    static const struct revisions_changed_results test_data[25] = {
 
633
      /* path,          num,    revisions changed... */
 
634
      { "",              11,    { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } },
 
635
      { "iota",           4,    {        8,          4, 3,    1    } },
 
636
      { "A",             10,    { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1    } },
 
637
      { "A/mu",           3,    {                 5,       2, 1    } },
 
638
      { "A/B",            5,    {                 5, 4, 3, 2, 1    } },
 
639
      { "A/B/lambda",     2,    {                       3,    1    } },
 
640
      { "A/B/E",          4,    {                 5, 4,    2, 1    } },
 
641
      { "A/B/E/alpha",    3,    {                 5,       2, 1    } },
 
642
      { "A/B/E/beta",     2,    {                    4,       1    } },
 
643
      { "A/B/F",          1,    {                             1    } },
 
644
      { "A/C",            1,    {                             1    } },
 
645
      { "A/D",           10,    { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1    } },
 
646
      { "A/D/gamma",      4,    {        8,    6,    4,       1    } },
 
647
      { "A/D/G",          6,    {        8, 7, 6, 5, 4,       1    } },
 
648
      { "A/D/G/pi",       5,    {        8, 7, 6,    4,       1    } },
 
649
      { "A/D/G/rho",      4,    {        8,    6,    4,       1    } },
 
650
      { "A/D/G/tau",      4,    {        8,    6, 5,          1    } },
 
651
      { "A/D/Q",          8,    { 10, 9, 8, 7, 6, 5, 4,       1    } },
 
652
      { "A/D/Q/pi",       7,    { 10, 9, 8, 7, 6,    4,       1    } },
 
653
      { "A/D/Q/rho",      6,    { 10, 9, 8,    6,    4,       1    } },
 
654
      { "A/D/Q/tau",      5,    {     9, 8,    6, 5,          1    } },
 
655
      { "A/D/H",          6,    {        8,    6, 5,    3, 2, 1    } },
 
656
      { "A/D/H/chi",      4,    {        8,    6, 5,          1    } },
 
657
      { "A/D/H/psi",      4,    {        8,    6,       3,    1    } },
 
658
      { "A/D/H/omega",    5,    {        8,    6,       3, 2, 1    } }
 
659
    };
 
660
    
 
661
    /* Now, for each path in the revision, get its changed-revisions
 
662
       array and compare the array to the static results above.  */
 
663
    for (j = 0; j < 25; j++)
 
664
      {
 
665
        int i;
 
666
        const char *path = test_data[j].path;
 
667
        int num_revs = test_data[j].num_revs;
 
668
        const svn_revnum_t *revs_changed = test_data[j].revs_changed;
 
669
        apr_array_header_t *revs = apr_array_make (spool, 10, 
 
670
                                                   sizeof (svn_revnum_t));
 
671
 
 
672
        SVN_ERR (svn_repos_history (fs, path, history_to_revs_array, revs, 
 
673
                                    0, youngest_rev, TRUE, spool));
 
674
 
 
675
        /* Are we at least looking at the right number of returned
 
676
           revisions? */
 
677
        if ((! revs) || (revs->nelts != num_revs))
 
678
          return svn_error_createf
 
679
            (SVN_ERR_FS_GENERAL, NULL,
 
680
             "Changed revisions differ from expected for '%s'\n%s",
 
681
             path, print_chrevs (revs, num_revs, revs_changed, spool));
 
682
 
 
683
        /* Do the revisions lists match up exactly? */
 
684
        for (i = 0; i < num_revs; i++)
 
685
          {
 
686
            svn_revnum_t rev = ((svn_revnum_t *)revs->elts)[i];
 
687
            if (rev != revs_changed[i])
 
688
              return svn_error_createf
 
689
                (SVN_ERR_FS_GENERAL, NULL,
 
690
                 "Changed revisions differ from expected for '%s'\n%s",
 
691
                 path, print_chrevs (revs, num_revs, revs_changed, spool));
 
692
          }
 
693
        
 
694
        /* Clear the per-iteration subpool. */
 
695
        svn_pool_clear (spool);
 
696
      }
 
697
  }
 
698
 
 
699
  /* Destroy the subpool. */
 
700
  svn_pool_destroy (spool);
 
701
 
 
702
  return SVN_NO_ERROR;
 
703
}
 
704
 
 
705
 
 
706
 
 
707
struct locations_info
 
708
{
 
709
  svn_revnum_t rev;
 
710
  const char *path;
 
711
};
 
712
 
 
713
/* Check that LOCATIONS contain everything in INFO and nothing more. */
 
714
static svn_error_t *
 
715
check_locations_info (apr_hash_t *locations, const struct locations_info *info)
 
716
{
 
717
  unsigned int i;
 
718
  for (i = 0; info->rev != 0; ++i, ++info)
 
719
    {
 
720
      const char *p = apr_hash_get (locations, &info->rev, sizeof
 
721
                                    (svn_revnum_t));
 
722
      if (!p)
 
723
        return svn_error_createf (SVN_ERR_TEST_FAILED, NULL,
 
724
                                  "Missing path for revision %ld", info->rev);
 
725
      if (strcmp (p, info->path) != 0)
 
726
        return svn_error_createf (SVN_ERR_TEST_FAILED, NULL,
 
727
                                  "Pth mismatch for rev %ld", info->rev);
 
728
    }
 
729
 
 
730
  if (apr_hash_count (locations) > i)
 
731
    return svn_error_create (SVN_ERR_TEST_FAILED, NULL,
 
732
                             "Returned locations contain too many elements.");
 
733
  
 
734
  return SVN_NO_ERROR;
 
735
}
 
736
 
 
737
/* Check that all locations in INFO exist in REPOS for PATH and PEG_REVISION.
 
738
 */
 
739
static svn_error_t *
 
740
check_locations (svn_fs_t *fs, struct locations_info *info,
 
741
                 const char *path, svn_revnum_t peg_revision,
 
742
                 apr_pool_t *pool)
 
743
{
 
744
  apr_array_header_t *a = apr_array_make (pool, 0, sizeof (svn_revnum_t));
 
745
  apr_hash_t *h;
 
746
  struct locations_info *iter;
 
747
 
 
748
  for (iter = info; iter->rev != 0; ++iter)
 
749
    *(svn_revnum_t *) apr_array_push (a) = iter->rev;
 
750
 
 
751
  SVN_ERR (svn_repos_trace_node_locations (fs, &h, path, peg_revision, a,
 
752
                                           NULL, NULL, pool));
 
753
  SVN_ERR (check_locations_info (h, info));
 
754
 
 
755
  return SVN_NO_ERROR;
 
756
}
 
757
 
 
758
static svn_error_t *
 
759
node_locations (const char **msg, 
 
760
                svn_boolean_t msg_only, 
 
761
                svn_test_opts_t *opts,
 
762
                apr_pool_t *pool)
 
763
{
 
764
  apr_pool_t *subpool = svn_pool_create (pool);
 
765
  svn_repos_t *repos;
 
766
  svn_fs_t *fs;
 
767
  svn_fs_txn_t *txn;
 
768
  svn_fs_root_t *txn_root, *root;
 
769
  svn_revnum_t youngest_rev;
 
770
 
 
771
  *msg = "test svn_repos_node_locations";
 
772
  if (msg_only)
 
773
    return SVN_NO_ERROR;
 
774
 
 
775
  /* Create the repository with a Greek tree. */
 
776
  SVN_ERR (svn_test__create_repos (&repos, "test-repo-node-locations", 
 
777
                                   opts->fs_type, pool));
 
778
  fs = svn_repos_fs (repos);
 
779
  SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, subpool));
 
780
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
781
  SVN_ERR (svn_test__create_greek_tree (txn_root, subpool));
 
782
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, subpool));
 
783
  svn_pool_clear (subpool);
 
784
 
 
785
  /* Move a file. Rev 2. */
 
786
  SVN_ERR (svn_fs_revision_root (&root, fs, youngest_rev, subpool));
 
787
  SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, subpool));
 
788
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
789
  SVN_ERR (svn_fs_copy (root, "/A/mu", txn_root, "/mu.new", subpool));
 
790
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, subpool));
 
791
  {
 
792
    struct locations_info info[] =
 
793
      {
 
794
        { 1, "/A/mu" },
 
795
        { 2, "/mu.new" },
 
796
        { 0 }
 
797
      };
 
798
    SVN_ERR (check_locations (fs, info, "/mu.new", 2, pool));
 
799
  }
 
800
  svn_pool_clear (subpool);
 
801
  
 
802
  return SVN_NO_ERROR;
 
803
}
 
804
 
 
805
 
 
806
 
 
807
/* Testing the reporter. */
 
808
 
 
809
/* Functions for an editor that will catch removal of defunct locks. */
 
810
 
 
811
/* The main editor baton. */
 
812
typedef struct rmlocks_baton_t {
 
813
  apr_hash_t *removed;
 
814
  apr_pool_t *pool;
 
815
} rmlocks_baton_t;
 
816
 
 
817
/* The file baton. */
 
818
typedef struct rmlocks_file_baton_t {
 
819
  rmlocks_baton_t *main_baton;
 
820
  const char *path;
 
821
} rmlocks_file_baton_t;
 
822
 
 
823
/* An svn_delta_editor_t function. */
 
824
static svn_error_t *
 
825
rmlocks_open_file (const char *path,
 
826
                   void *parent_baton,
 
827
                   svn_revnum_t base_revision,
 
828
                   apr_pool_t *file_pool,
 
829
                   void **file_baton)
 
830
{
 
831
  rmlocks_file_baton_t *fb = apr_palloc (file_pool, sizeof (*fb));
 
832
  rmlocks_baton_t *b = parent_baton;
 
833
 
 
834
  fb->main_baton = b;
 
835
  fb->path = apr_pstrdup (b->pool, path);
 
836
 
 
837
  *file_baton = fb;
 
838
 
 
839
  return SVN_NO_ERROR;
 
840
}
 
841
 
 
842
/* An svn_delta_editor_t function. */
 
843
static svn_error_t *
 
844
rmlocks_change_prop (void *file_baton,
 
845
                     const char *name,
 
846
                     const svn_string_t *value,
 
847
                     apr_pool_t *pool)
 
848
{
 
849
  rmlocks_file_baton_t *fb = file_baton;
 
850
 
 
851
  if (strcmp (name, SVN_PROP_ENTRY_LOCK_TOKEN) == 0)
 
852
    {
 
853
      if (value != NULL)
 
854
        return svn_error_create (SVN_ERR_TEST_FAILED, NULL,
 
855
                                 "Value for lock-token property not NULL");
 
856
 
 
857
      /* We only want it removed once. */
 
858
      if (apr_hash_get (fb->main_baton->removed, fb->path,
 
859
                        APR_HASH_KEY_STRING) != NULL)
 
860
        return svn_error_createf (SVN_ERR_TEST_FAILED, NULL,
 
861
                                  "Lock token for '%s' already removed",
 
862
                                  fb->path);
 
863
 
 
864
      /* Mark as removed. */
 
865
      apr_hash_set (fb->main_baton->removed, fb->path, APR_HASH_KEY_STRING,
 
866
                    (void *)1);
 
867
    }
 
868
 
 
869
  return SVN_NO_ERROR;
 
870
}
 
871
 
 
872
/* An svn_delta_editor_t function. */
 
873
static svn_error_t *
 
874
rmlocks_open_root (void *edit_baton,
 
875
                   svn_revnum_t base_revision,
 
876
                   apr_pool_t *dir_pool,
 
877
                   void **root_baton)
 
878
{
 
879
  *root_baton = edit_baton;
 
880
  return SVN_NO_ERROR;
 
881
}
 
882
 
 
883
/* An svn_delta_editor_t function. */
 
884
static svn_error_t *
 
885
rmlocks_open_directory (const char *path,
 
886
                        void *parent_baton,
 
887
                        svn_revnum_t base_revision,
 
888
                        apr_pool_t *pool,
 
889
                        void **dir_baton)
 
890
{
 
891
  *dir_baton = parent_baton;
 
892
  return SVN_NO_ERROR;
 
893
}
 
894
 
 
895
/* Create an svn_delta_editor/baton, storing them in EDITOR/EDIT_BATON,
 
896
   that will store paths for which lock tokens were *REMOVED in REMOVED.
 
897
   Allocate the editor and *REMOVED in POOL. */
 
898
static svn_error_t *
 
899
create_rmlocks_editor (svn_delta_editor_t **editor,
 
900
                       void **edit_baton,
 
901
                       apr_hash_t **removed,
 
902
                       apr_pool_t *pool)
 
903
{
 
904
  rmlocks_baton_t *baton = apr_palloc (pool, sizeof (*baton));
 
905
 
 
906
  /* Create the editor. */
 
907
  *editor = svn_delta_default_editor (pool);
 
908
  (*editor)->open_root = rmlocks_open_root;
 
909
  (*editor)->open_directory = rmlocks_open_directory;
 
910
  (*editor)->open_file = rmlocks_open_file;
 
911
  (*editor)->change_file_prop = rmlocks_change_prop;
 
912
 
 
913
  /* Initialize the baton. */
 
914
  baton->removed = apr_hash_make (pool);
 
915
  baton->pool = pool;
 
916
  *edit_baton = baton;
 
917
 
 
918
  *removed = baton->removed;
 
919
 
 
920
  return SVN_NO_ERROR;
 
921
}
 
922
 
 
923
/* Check that HASH contains exactly the const char * entries for all entries
 
924
   in the NULL-terminated array SPEC. */
 
925
static svn_error_t *
 
926
rmlocks_check (const char **spec, apr_hash_t *hash)
 
927
{
 
928
  apr_size_t n = 0;
 
929
 
 
930
  for (; *spec; ++spec, ++n)
 
931
    {
 
932
      if (! apr_hash_get (hash, *spec, APR_HASH_KEY_STRING))
 
933
        return svn_error_createf
 
934
          (SVN_ERR_TEST_FAILED, NULL,
 
935
           "Lock token for '%s' should have been removed", *spec);
 
936
    }
 
937
 
 
938
  if (n < apr_hash_count (hash))
 
939
    return svn_error_create (SVN_ERR_TEST_FAILED, NULL,
 
940
                             "Lock token for one or more paths unexpectedly "
 
941
                             "removed");
 
942
  return SVN_NO_ERROR;
 
943
}
 
944
 
 
945
/* Test that defunct locks are removed by the reporter. */
 
946
static svn_error_t *
 
947
rmlocks (const char **msg,
 
948
         svn_boolean_t msg_only,
 
949
         svn_test_opts_t *opts,
 
950
         apr_pool_t *pool)
 
951
{
 
952
  svn_repos_t *repos;
 
953
  svn_fs_t *fs;
 
954
  svn_fs_txn_t *txn;
 
955
  svn_fs_root_t *txn_root;
 
956
  apr_pool_t *subpool = svn_pool_create (pool);
 
957
  svn_revnum_t youngest_rev;
 
958
  svn_delta_editor_t *editor;
 
959
  void *edit_baton, *report_baton;
 
960
  svn_lock_t *l1, *l2, *l3, *l4;
 
961
  svn_fs_access_t *fs_access;
 
962
  apr_hash_t *removed;
 
963
 
 
964
  *msg = "test removal of defunct locks";
 
965
 
 
966
  if (msg_only)
 
967
    return SVN_NO_ERROR;
 
968
 
 
969
  /* Create a filesystem and repository. */
 
970
  SVN_ERR (svn_test__create_repos (&repos, "test-repo-rmlocks", 
 
971
                                   opts->fs_type, pool));
 
972
  fs = svn_repos_fs (repos);
 
973
 
 
974
  /* Prepare a txn to receive the greek tree. */
 
975
  SVN_ERR (svn_fs_begin_txn (&txn, fs, 0, subpool));
 
976
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, subpool));
 
977
  SVN_ERR (svn_test__create_greek_tree (txn_root, subpool));
 
978
  SVN_ERR (svn_repos_fs_commit_txn (NULL, repos, &youngest_rev, txn, subpool));
 
979
  svn_pool_clear (subpool);
 
980
 
 
981
  SVN_ERR (svn_fs_create_access (&fs_access, "user1", pool));
 
982
  SVN_ERR (svn_fs_set_access (fs, fs_access));
 
983
 
 
984
  /* Lock some files, break a lock, steal another and check that those get
 
985
     removed. */
 
986
  {
 
987
    const char *expected [] = { "A/mu", "A/D/gamma", NULL };
 
988
 
 
989
    SVN_ERR (svn_fs_lock (&l1, fs, "/iota", NULL, NULL, 0, 0, youngest_rev,
 
990
                          FALSE, subpool));
 
991
    SVN_ERR (svn_fs_lock (&l2, fs, "/A/mu", NULL, NULL, 0, 0, youngest_rev,
 
992
                          FALSE, subpool));
 
993
    SVN_ERR (svn_fs_lock (&l3, fs, "/A/D/gamma", NULL, NULL, 0, 0, youngest_rev,
 
994
                          FALSE, subpool));
 
995
 
 
996
    /* Break l2. */
 
997
    SVN_ERR (svn_fs_unlock (fs, "/A/mu", NULL, TRUE, subpool));
 
998
 
 
999
    /* Steal l3 from ourselves. */
 
1000
    SVN_ERR (svn_fs_lock (&l4, fs, "/A/D/gamma", NULL, NULL, 0, 0, youngest_rev,
 
1001
                          TRUE, subpool));
 
1002
 
 
1003
    /* Create the editor. */
 
1004
    SVN_ERR (create_rmlocks_editor (&editor, &edit_baton, &removed, subpool));
 
1005
 
 
1006
    /* Report what we have. */
 
1007
    SVN_ERR (svn_repos_begin_report (&report_baton, 1, "user1", repos, "/", "",
 
1008
                                     NULL, FALSE, TRUE, FALSE, editor,
 
1009
                                     edit_baton, NULL, NULL, subpool));
 
1010
    SVN_ERR (svn_repos_set_path2 (report_baton, "", 1, FALSE, NULL,
 
1011
                                  subpool));
 
1012
    SVN_ERR (svn_repos_set_path2 (report_baton, "iota", 1, FALSE, l1->token,
 
1013
                                  subpool));
 
1014
    SVN_ERR (svn_repos_set_path2 (report_baton, "A/mu", 1, FALSE, l2->token,
 
1015
                                  subpool));
 
1016
    SVN_ERR (svn_repos_set_path2 (report_baton, "A/D/gamma", 1, FALSE,
 
1017
                                  l3->token, subpool));
 
1018
    
 
1019
    /* End the report. */
 
1020
    SVN_ERR (svn_repos_finish_report (report_baton, pool));
 
1021
 
 
1022
    /* And check that the edit did what we wanted. */
 
1023
    SVN_ERR (rmlocks_check (expected, removed));
 
1024
  }
 
1025
 
 
1026
  svn_pool_destroy (subpool);
 
1027
 
 
1028
  return SVN_NO_ERROR;
 
1029
}
 
1030
 
 
1031
/* The test table.  */
 
1032
 
 
1033
struct svn_test_descriptor_t test_funcs[] =
 
1034
  {
 
1035
    SVN_TEST_NULL,
 
1036
    SVN_TEST_PASS (dir_deltas),
 
1037
    SVN_TEST_PASS (node_tree_delete_under_copy),
 
1038
    SVN_TEST_PASS (revisions_changed),
 
1039
    SVN_TEST_PASS (node_locations),
 
1040
    SVN_TEST_PASS (rmlocks),
 
1041
    SVN_TEST_NULL
 
1042
  };