1 /* changes-test.c --- test `changes' interfaces
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22 
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <stdio.h>
27 
28 #include <apr.h>
29 
30 #include "../svn_test.h"
31 
32 #include "svn_pools.h"
33 #include "svn_error.h"
34 #include "private/svn_skel.h"
35 
36 #include "../svn_test_fs.h"
37 #include "../../libsvn_fs_base/util/fs_skels.h"
38 #include "../../libsvn_fs_base/bdb/changes-table.h"
39 
40 
41 
42 /* Helper functions/variables.  */
43 static const char *standard_txns[]
44   = { "0", "1", "2", "3", "4", "5", "6" };
45 static const char *standard_changes[19][6]
46      /* KEY   PATH   NODEREVID  KIND     TEXT PROP */
47   = { { "0",  "/foo",  "1.0.0",  "add",     0,  0  },
48       { "0",  "/foo",  "1.0.0",  "modify",  0, "1" },
49       { "0",  "/bar",  "2.0.0",  "add",     0,  0  },
50       { "0",  "/bar",  "2.0.0",  "modify", "1", 0  },
51       { "0",  "/bar",  "2.0.0",  "modify",  0, "1" },
52       { "0",  "/baz",  "3.0.0",  "add",     0,  0  },
53       { "0",  "/baz",  "3.0.0",  "modify", "1", 0  },
54       { "1",  "/foo",  "1.0.1",  "modify", "1", 0  },
55       { "2",  "/foo",  "1.0.2",  "modify",  0, "1" },
56       { "2",  "/bar",  "2.0.2",  "modify", "1", 0  },
57       { "3",  "/baz",  "3.0.3",  "modify", "1", 0  },
58       { "4",  "/fob",  "4.0.4",  "add",     0,  0  },
59       { "4",  "/fob",  "4.0.4",  "modify", "1", 0  },
60       { "5",  "/baz",  "3.0.3",  "delete",  0,  0  },
61       { "5",  "/baz",  "5.0.5",  "add",     0, "1" },
62       { "5",  "/baz",  "5.0.5",  "modify", "1", 0  },
63       { "6",  "/fob",  "4.0.6",  "modify", "1", 0  },
64       { "6",  "/fob",  "4.0.6",  "reset",   0,  0  },
65       { "6",  "/fob",  "4.0.6",  "modify",  0, "1" } };
66 
67 
string_to_kind(const char * str)68 static svn_fs_path_change_kind_t string_to_kind(const char *str)
69 {
70   if (strcmp(str, "add") == 0)
71     return svn_fs_path_change_add;
72   if (strcmp(str, "delete") == 0)
73     return svn_fs_path_change_delete;
74   if (strcmp(str, "replace") == 0)
75     return svn_fs_path_change_replace;
76   if (strcmp(str, "modify") == 0)
77     return svn_fs_path_change_modify;
78   if (strcmp(str, "reset") == 0)
79     return svn_fs_path_change_reset;
80   return 0;
81 }
82 
83 
84 /* Common args structure for several different txn_body_* functions. */
85 struct changes_args
86 {
87   svn_fs_t *fs;
88   const char *key;
89   change_t *change;
90   apr_array_header_t *raw_changes;
91   apr_hash_t *changes;
92 };
93 
94 
95 static svn_error_t *
txn_body_changes_add(void * baton,trail_t * trail)96 txn_body_changes_add(void *baton, trail_t *trail)
97 {
98   struct changes_args *b = baton;
99   return svn_fs_bdb__changes_add(b->fs, b->key, b->change,
100                                  trail, trail->pool);
101 }
102 
103 
104 static svn_error_t *
add_standard_changes(svn_fs_t * fs,apr_pool_t * pool)105 add_standard_changes(svn_fs_t *fs,
106                      apr_pool_t *pool)
107 {
108   int i;
109   struct changes_args args;
110   int num_changes = sizeof(standard_changes) / sizeof(const char *) / 6;
111 
112   for (i = 0; i < num_changes; i++)
113     {
114       change_t change;
115 
116       /* Set up the current change item. */
117       change.path = standard_changes[i][1];
118       change.noderev_id = svn_fs_parse_id(standard_changes[i][2],
119                                           strlen(standard_changes[i][2]),
120                                           pool);
121       change.kind = string_to_kind(standard_changes[i][3]);
122       change.text_mod = standard_changes[i][4] ? 1 : 0;
123       change.prop_mod = standard_changes[i][5] ? 1 : 0;
124 
125       /* Set up transaction baton. */
126       args.fs = fs;
127       args.key = standard_changes[i][0];
128       args.change = &change;
129 
130       /* Write new changes to the changes table. */
131       SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_add, &args,
132                                      TRUE, pool));
133     }
134 
135   return SVN_NO_ERROR;
136 }
137 
138 
139 static svn_error_t *
txn_body_changes_fetch_raw(void * baton,trail_t * trail)140 txn_body_changes_fetch_raw(void *baton, trail_t *trail)
141 {
142   struct changes_args *b = baton;
143   return svn_fs_bdb__changes_fetch_raw(&(b->raw_changes), b->fs, b->key,
144                                        trail, trail->pool);
145 }
146 
147 
148 static svn_error_t *
txn_body_changes_fetch(void * baton,trail_t * trail)149 txn_body_changes_fetch(void *baton, trail_t *trail)
150 {
151   struct changes_args *b = baton;
152   return svn_fs_bdb__changes_fetch(&(b->changes), b->fs, b->key,
153                                    trail, trail->pool);
154 }
155 
156 
157 static svn_error_t *
txn_body_changes_delete(void * baton,trail_t * trail)158 txn_body_changes_delete(void *baton, trail_t *trail)
159 {
160   struct changes_args *b = baton;
161   return svn_fs_bdb__changes_delete(b->fs, b->key, trail, trail->pool);
162 }
163 
164 
165 
166 /* The tests.  */
167 
168 static svn_error_t *
changes_add(const svn_test_opts_t * opts,apr_pool_t * pool)169 changes_add(const svn_test_opts_t *opts,
170             apr_pool_t *pool)
171 {
172   svn_fs_t *fs;
173 
174   /* Create a new fs and repos */
175   SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-add", opts,
176                                   pool));
177 
178   /* Add the standard slew of changes. */
179   SVN_ERR(add_standard_changes(fs, pool));
180 
181   return SVN_NO_ERROR;
182 }
183 
184 
185 static svn_error_t *
changes_fetch_raw(const svn_test_opts_t * opts,apr_pool_t * pool)186 changes_fetch_raw(const svn_test_opts_t *opts,
187                   apr_pool_t *pool)
188 {
189   svn_fs_t *fs;
190   int i;
191   int num_txns = sizeof(standard_txns) / sizeof(const char *);
192   int cur_change_index = 0;
193   struct changes_args args;
194 
195   /* Create a new fs and repos */
196   SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-fetch-raw", opts,
197                                   pool));
198 
199   /* First, verify that we can request changes for an arbitrary key
200      without error. */
201   args.fs = fs;
202   args.key = "blahbliggityblah";
203   SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch_raw, &args,
204                                  FALSE, pool));
205   if ((! args.raw_changes) || (args.raw_changes->nelts))
206     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
207                             "expected empty changes array");
208 
209   /* Add the standard slew of changes. */
210   SVN_ERR(add_standard_changes(fs, pool));
211 
212   /* For each transaction, fetch that transaction's changes, and
213      compare those changes against the standard changes list.  Order
214      matters throughout all the changes code, so we shouldn't have to
215      worry about ordering of the arrays.  */
216   for (i = 0; i < num_txns; i++)
217     {
218       const char *txn_id = standard_txns[i];
219       int j;
220 
221       /* Setup the trail baton. */
222       args.fs = fs;
223       args.key = txn_id;
224 
225       /* And get those changes. */
226       SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch_raw,
227                                      &args, FALSE, pool));
228       if (! args.raw_changes)
229         return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
230                                  "got no changes for key '%s'", txn_id);
231 
232       for (j = 0; j < args.raw_changes->nelts; j++)
233         {
234           svn_string_t *noderev_id;
235           svn_fs_path_change_kind_t kind;
236           change_t *change = APR_ARRAY_IDX(args.raw_changes, j, change_t *);
237           int mod_bit = 0;
238 
239           /* Verify that the TXN_ID matches. */
240           if (strcmp(standard_changes[cur_change_index][0], txn_id))
241             return svn_error_createf
242               (SVN_ERR_TEST_FAILED, NULL,
243                "missing some changes for key '%s'", txn_id);
244 
245           /* Verify that the PATH matches. */
246           if (strcmp(standard_changes[cur_change_index][1], change->path))
247             return svn_error_createf
248               (SVN_ERR_TEST_FAILED, NULL,
249                "paths differ in change for key '%s'", txn_id);
250 
251           /* Verify that the NODE-REV-ID matches. */
252           noderev_id = svn_fs_unparse_id(change->noderev_id, pool);
253           if (strcmp(standard_changes[cur_change_index][2], noderev_id->data))
254             return svn_error_createf
255               (SVN_ERR_TEST_FAILED, NULL,
256                "node revision ids differ in change for key '%s'", txn_id);
257 
258           /* Verify that the change KIND matches. */
259           kind = string_to_kind(standard_changes[cur_change_index][3]);
260           if (kind != change->kind)
261             return svn_error_createf
262               (SVN_ERR_TEST_FAILED, NULL,
263                "change kinds differ in change for key '%s'", txn_id);
264 
265           /* Verify that the change TEXT-MOD bit matches. */
266           mod_bit = standard_changes[cur_change_index][4] ? 1 : 0;
267           if (mod_bit != change->text_mod)
268             return svn_error_createf
269               (SVN_ERR_TEST_FAILED, NULL,
270                "change text-mod bits differ in change for key '%s'", txn_id);
271 
272           /* Verify that the change PROP-MOD bit matches. */
273           mod_bit = standard_changes[cur_change_index][5] ? 1 : 0;
274           if (mod_bit != change->prop_mod)
275             return svn_error_createf
276               (SVN_ERR_TEST_FAILED, NULL,
277                "change prop-mod bits differ in change for key '%s'", txn_id);
278 
279           cur_change_index++;
280         }
281     }
282 
283   return SVN_NO_ERROR;
284 }
285 
286 
287 static svn_error_t *
changes_delete(const svn_test_opts_t * opts,apr_pool_t * pool)288 changes_delete(const svn_test_opts_t *opts,
289                apr_pool_t *pool)
290 {
291   svn_fs_t *fs;
292   int i;
293   int num_txns = sizeof(standard_txns) / sizeof(const char *);
294   struct changes_args args;
295 
296   /* Create a new fs and repos */
297   SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-delete", opts,
298                                   pool));
299 
300   /* Add the standard slew of changes. */
301   SVN_ERR(add_standard_changes(fs, pool));
302 
303   /* Now, delete all the changes we know about, verifying their removal. */
304   for (i = 0; i < num_txns; i++)
305     {
306       args.fs = fs;
307       args.key = standard_txns[i];
308       SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_delete,
309                                      &args, FALSE, pool));
310       args.changes = 0;
311       SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch_raw,
312                                      &args, FALSE, pool));
313       if ((! args.raw_changes) || (args.raw_changes->nelts))
314         return svn_error_createf
315           (SVN_ERR_TEST_FAILED, NULL,
316            "expected empty changes array for txn '%s'", args.key);
317     }
318 
319   return SVN_NO_ERROR;
320 }
321 
322 
323 static apr_hash_t *
get_ideal_changes(const char * txn_id,apr_pool_t * pool)324 get_ideal_changes(const char *txn_id,
325                   apr_pool_t *pool)
326 {
327   apr_hash_t *ideal = apr_hash_make(pool);
328   svn_fs_path_change_t *change;
329   if (strcmp(txn_id, "0") == 0)
330     {
331       change = apr_palloc(pool, sizeof(*change));
332       change->node_rev_id = svn_fs_parse_id("1.0.0", 5, pool);
333       change->change_kind = svn_fs_path_change_add;
334       change->text_mod = 0;
335       change->prop_mod = 1;
336       apr_hash_set(ideal, "/foo", APR_HASH_KEY_STRING, change);
337 
338       change = apr_palloc(pool, sizeof(*change));
339       change->node_rev_id = svn_fs_parse_id("2.0.0", 5, pool);
340       change->change_kind = svn_fs_path_change_add;
341       change->text_mod = 1;
342       change->prop_mod = 1;
343       apr_hash_set(ideal, "/bar", APR_HASH_KEY_STRING, change);
344 
345       change = apr_palloc(pool, sizeof(*change));
346       change->node_rev_id = svn_fs_parse_id("3.0.0", 5, pool);
347       change->change_kind = svn_fs_path_change_add;
348       change->text_mod = 1;
349       change->prop_mod = 0;
350       apr_hash_set(ideal, "/baz", APR_HASH_KEY_STRING, change);
351     }
352   if (strcmp(txn_id, "1") == 0)
353     {
354       change = apr_palloc(pool, sizeof(*change));
355       change->node_rev_id = svn_fs_parse_id("1.0.1", 5, pool);
356       change->change_kind = svn_fs_path_change_modify;
357       change->text_mod = 1;
358       change->prop_mod = 0;
359       apr_hash_set(ideal, "/foo", APR_HASH_KEY_STRING, change);
360     }
361   if (strcmp(txn_id, "2") == 0)
362     {
363       change = apr_palloc(pool, sizeof(*change));
364       change->node_rev_id = svn_fs_parse_id("1.0.2", 5, pool);
365       change->change_kind = svn_fs_path_change_modify;
366       change->text_mod = 0;
367       change->prop_mod = 1;
368       apr_hash_set(ideal, "/foo", APR_HASH_KEY_STRING, change);
369 
370       change = apr_palloc(pool, sizeof(*change));
371       change->node_rev_id = svn_fs_parse_id("2.0.2", 5, pool);
372       change->change_kind = svn_fs_path_change_modify;
373       change->text_mod = 1;
374       change->prop_mod = 0;
375       apr_hash_set(ideal, "/bar", APR_HASH_KEY_STRING, change);
376     }
377   if (strcmp(txn_id, "3") == 0)
378     {
379       change = apr_palloc(pool, sizeof(*change));
380       change->node_rev_id = svn_fs_parse_id("3.0.3", 5, pool);
381       change->change_kind = svn_fs_path_change_modify;
382       change->text_mod = 1;
383       change->prop_mod = 0;
384       apr_hash_set(ideal, "/baz", APR_HASH_KEY_STRING, change);
385     }
386   if (strcmp(txn_id, "4") == 0)
387     {
388       change = apr_palloc(pool, sizeof(*change));
389       change->node_rev_id = svn_fs_parse_id("4.0.4", 5, pool);
390       change->change_kind = svn_fs_path_change_add;
391       change->text_mod = 1;
392       change->prop_mod = 0;
393       apr_hash_set(ideal, "/fob", APR_HASH_KEY_STRING, change);
394     }
395   if (strcmp(txn_id, "5") == 0)
396     {
397       change = apr_palloc(pool, sizeof(*change));
398       change->node_rev_id = svn_fs_parse_id("5.0.5", 5, pool);
399       change->change_kind = svn_fs_path_change_replace;
400       change->text_mod = 1;
401       change->prop_mod = 1;
402       apr_hash_set(ideal, "/baz", APR_HASH_KEY_STRING, change);
403     }
404   if (strcmp(txn_id, "6") == 0)
405     {
406       change = apr_palloc(pool, sizeof(*change));
407       change->node_rev_id = svn_fs_parse_id("4.0.6", 5, pool);
408       change->change_kind = svn_fs_path_change_modify;
409       change->text_mod = 0;
410       change->prop_mod = 1;
411       apr_hash_set(ideal, "/fob", APR_HASH_KEY_STRING, change);
412     }
413   return ideal;
414 }
415 
416 
417 static svn_error_t *
compare_changes(apr_hash_t * ideals,apr_hash_t * changes,const svn_test_opts_t * opts,const char * txn_id,apr_pool_t * pool)418 compare_changes(apr_hash_t *ideals,
419                 apr_hash_t *changes,
420                 const svn_test_opts_t *opts,
421                 const char *txn_id,
422                 apr_pool_t *pool)
423 {
424   apr_hash_index_t *hi;
425 
426   for (hi = apr_hash_first(pool, ideals); hi; hi = apr_hash_next(hi))
427     {
428       const void *key;
429       void *val;
430       svn_fs_path_change_t *ideal_change, *change;
431       const char *path;
432 
433       /* KEY will be the path, VAL the change. */
434       apr_hash_this(hi, &key, NULL, &val);
435       path = (const char *) key;
436       ideal_change = val;
437 
438       /* Now get the change that refers to PATH in the actual
439          changes hash. */
440       change = apr_hash_get(changes, path, APR_HASH_KEY_STRING);
441       if (! change)
442         return svn_error_createf
443           (SVN_ERR_TEST_FAILED, NULL,
444            "missing expected change for path '%s' in txn_id '%s'",
445            path, txn_id);
446 
447       /* Verify that the NODE-REV-ID matches. */
448       if (svn_fs_compare_ids(change->node_rev_id,
449                              ideal_change->node_rev_id))
450         return svn_error_createf
451           (SVN_ERR_TEST_FAILED, NULL,
452            "node revision ids differ in change for key '%s'", txn_id);
453 
454       /* Verify that the change KIND matches. */
455       if (change->change_kind != ideal_change->change_kind)
456         return svn_error_createf
457           (SVN_ERR_TEST_FAILED, NULL,
458            "change kinds differ in change for key '%s'", txn_id);
459 
460       /* Verify that the change TEXT-MOD bit matches. */
461       if (change->text_mod != ideal_change->text_mod)
462         return svn_error_createf
463           (SVN_ERR_TEST_FAILED, NULL,
464            "change text-mod bits differ in change for key '%s'", txn_id);
465 
466       /* Verify that the change PROP-MOD bit matches. */
467       if (change->prop_mod != ideal_change->prop_mod)
468         return svn_error_createf
469           (SVN_ERR_TEST_FAILED, NULL,
470            "change prop-mod bits differ in change for key '%s'", txn_id);
471     }
472 
473   return SVN_NO_ERROR;
474 }
475 
476 
477 static svn_error_t *
changes_fetch(const svn_test_opts_t * opts,apr_pool_t * pool)478 changes_fetch(const svn_test_opts_t *opts,
479               apr_pool_t *pool)
480 {
481   svn_fs_t *fs;
482   int i;
483   int num_txns = sizeof(standard_txns) / sizeof(const char *);
484   struct changes_args args;
485 
486   /* Create a new fs and repos */
487   SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-fetch", opts,
488                                   pool));
489 
490   /* First, verify that we can request changes for an arbitrary key
491      without error. */
492   args.fs = fs;
493   args.key = "blahbliggityblah";
494   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args,
495                                  FALSE, pool));
496   if ((! args.changes) || (apr_hash_count(args.changes)))
497     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
498                             "expected empty changes hash");
499 
500   /* Add the standard slew of changes. */
501   SVN_ERR(add_standard_changes(fs, pool));
502 
503   /* For each transaction, fetch that transaction's changes, and
504      compare those changes against our ideal compressed changes
505      hash. */
506   for (i = 0; i < num_txns; i++)
507     {
508       const char *txn_id = standard_txns[i];
509       apr_hash_t *ideals;
510 
511       /* Get the ideal changes hash. */
512       ideals = get_ideal_changes(txn_id, pool);
513 
514       /* Setup the trail baton. */
515       args.fs = fs;
516       args.key = txn_id;
517 
518       /* And get those changes via in the internal interface, and
519          verify that they are accurate. */
520       SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args,
521                                      FALSE, pool));
522       if (! args.changes)
523         return svn_error_createf
524           (SVN_ERR_TEST_FAILED, NULL,
525            "got no changes for key '%s'", txn_id);
526       if (apr_hash_count(ideals) != apr_hash_count(args.changes))
527         return svn_error_createf
528           (SVN_ERR_TEST_FAILED, NULL,
529            "unexpected number of changes for key '%s'", txn_id);
530       SVN_ERR(compare_changes(ideals, args.changes, opts, txn_id, pool));
531     }
532 
533   return SVN_NO_ERROR;
534 }
535 
536 
537 static svn_error_t *
changes_fetch_ordering(const svn_test_opts_t * opts,apr_pool_t * pool)538 changes_fetch_ordering(const svn_test_opts_t *opts,
539                        apr_pool_t *pool)
540 {
541   svn_fs_t *fs;
542   svn_revnum_t youngest_rev = 0;
543   const char *txn_name;
544   svn_fs_txn_t *txn;
545   svn_fs_root_t *txn_root, *rev_root;
546   struct changes_args args;
547   apr_pool_t *subpool = svn_pool_create(pool);
548   apr_hash_index_t *hi;
549 
550   /* Create a new fs and repos */
551   SVN_ERR(svn_test__create_bdb_fs
552           (&fs, "test-repo-changes-fetch-ordering", opts,
553            pool));
554 
555   /*** REVISION 1: Make some files and dirs. ***/
556   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
557   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
558   {
559     static svn_test__txn_script_command_t script_entries[] = {
560       { 'a', "dir1",        0 },
561       { 'a', "file1",       "This is the file 'file1'.\n" },
562       { 'a', "dir1/file2",  "This is the file 'file2'.\n" },
563       { 'a', "dir1/file3",  "This is the file 'file3'.\n" },
564       { 'a', "dir1/file4",  "This is the file 'file4'.\n" },
565     };
566     SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool));
567   }
568   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
569   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
570   svn_pool_clear(subpool);
571 
572   /*** REVISION 2: Delete and add some stuff, non-depth-first. ***/
573   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
574   /* Don't use subpool, txn_name is used after subpool is cleared */
575   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
576   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
577   {
578     static svn_test__txn_script_command_t script_entries[] = {
579       { 'd', "file1",       "This is the file 'file1'.\n" },
580       { 'd', "dir1/file2",  "This is the file 'file2'.\n" },
581       { 'd', "dir1/file3",  "This is the file 'file3'.\n" },
582       { 'a', "dir1/file5",  "This is the file 'file4'.\n" },
583       { 'a', "dir1/dir2",   0 },
584       { 'd', "dir1",        0 },
585       { 'a', "dir3",        0 },
586     };
587     SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 7, subpool));
588   }
589   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
590   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
591   svn_pool_clear(subpool);
592 
593   /*** TEST:  We should have only three changes, the deletion of 'file1'
594        the deletion of 'dir1', and the addition of 'dir3'. ***/
595   args.fs = fs;
596   args.key = txn_name;
597   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args,
598                                  FALSE, subpool));
599   if ((! args.changes) || (apr_hash_count(args.changes) != 3))
600     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
601                             "expected changes");
602   for (hi = apr_hash_first(subpool, args.changes);
603        hi; hi = apr_hash_next(hi))
604     {
605       const void *key;
606       void *val;
607       svn_fs_path_change_t *change;
608 
609       /* KEY will be the path, VAL the change. */
610       apr_hash_this(hi, &key, NULL, &val);
611       change = val;
612 
613       if ((change->change_kind == svn_fs_path_change_add)
614           && (strcmp(key, "/dir3") == 0))
615         ;
616       else if ((change->change_kind == svn_fs_path_change_delete)
617                && ((strcmp(key, "/dir1") == 0)
618                    || (strcmp(key, "/file1") == 0)))
619         ;
620       else
621         return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
622                                 "got wrong changes");
623     }
624 
625   /*** REVISION 3: Do the same stuff as in revision 1. ***/
626   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
627   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
628   {
629     static svn_test__txn_script_command_t script_entries[] = {
630       { 'a', "dir1",        0 },
631       { 'a', "file1",       "This is the file 'file1'.\n" },
632       { 'a', "dir1/file2",  "This is the file 'file2'.\n" },
633       { 'a', "dir1/file3",  "This is the file 'file3'.\n" },
634       { 'a', "dir1/file4",  "This is the file 'file4'.\n" },
635     };
636     SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool));
637   }
638   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
639   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
640   svn_pool_clear(subpool);
641 
642   /*** REVISION 4: Do the same stuff as in revision 2, but use a copy
643        overwrite of the top directory (instead of a delete) to test
644        that the 'replace' change type works, too.  (And add 'dir4'
645        instead of 'dir3', since 'dir3' still exists).  ***/
646   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
647   /* Don't use subpool, txn_name is used after subpool is cleared */
648   SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
649   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
650   SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, subpool));
651   {
652     static svn_test__txn_script_command_t script_entries[] = {
653       { 'd', "file1",       "This is the file 'file1'.\n" },
654       { 'd', "dir1/file2",  "This is the file 'file2'.\n" },
655       { 'd', "dir1/file3",  "This is the file 'file3'.\n" },
656       { 'a', "dir1/file5",  "This is the file 'file4'.\n" },
657       { 'a', "dir1/dir2",   0 },
658     };
659     SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool));
660     SVN_ERR(svn_fs_copy(rev_root, "dir1", txn_root, "dir1", subpool));
661     SVN_ERR(svn_fs_make_dir(txn_root, "dir4", subpool));
662   }
663   SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
664   SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
665   svn_pool_clear(subpool);
666 
667   /*** TEST:  We should have only three changes, the deletion of 'file1'
668        the replacement of 'dir1', and the addition of 'dir4'. ***/
669   args.fs = fs;
670   args.key = txn_name;
671   SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args,
672                                  FALSE, subpool));
673   if ((! args.changes) || (apr_hash_count(args.changes) != 3))
674     return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
675                             "expected changes");
676   for (hi = apr_hash_first(subpool, args.changes);
677        hi; hi = apr_hash_next(hi))
678     {
679       const void *key;
680       void *val;
681       svn_fs_path_change_t *change;
682 
683       /* KEY will be the path, VAL the change. */
684       apr_hash_this(hi, &key, NULL, &val);
685       change = val;
686 
687       if ((change->change_kind == svn_fs_path_change_add)
688           && (strcmp(key, "/dir4") == 0))
689         ;
690       else if ((change->change_kind == svn_fs_path_change_replace)
691                && (strcmp(key, "/dir1") == 0))
692         ;
693       else if ((change->change_kind == svn_fs_path_change_delete)
694                && (strcmp(key, "/file1") == 0))
695         ;
696       else
697         return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
698                                 "got wrong changes");
699     }
700 
701   return SVN_NO_ERROR;
702 }
703 
704 
705 static svn_error_t *
changes_bad_sequences(const svn_test_opts_t * opts,apr_pool_t * pool)706 changes_bad_sequences(const svn_test_opts_t *opts,
707                       apr_pool_t *pool)
708 {
709   svn_fs_t *fs;
710   apr_pool_t *subpool = svn_pool_create(pool);
711   svn_error_t *err;
712 
713   /* Create a new fs and repos */
714   SVN_ERR(svn_test__create_bdb_fs
715           (&fs, "test-repo-changes-bad-sequences", opts,
716            pool));
717 
718   /* Test changes bogus because a path's node-rev-ID changed
719      unexpectedly. */
720   svn_pool_clear(subpool);
721   {
722     static const char *bogus_changes[][6]
723          /* KEY   PATH   NODEREVID  KIND       TEXT PROP */
724       = { { "x",  "/foo",  "1.0.0",  "add",     0 ,  0  },
725           { "x",  "/foo",  "1.0.0",  "modify",  0 , "1" },
726           { "x",  "/foo",  "2.0.0",  "modify", "1", "1" } };
727     int num_changes = sizeof(bogus_changes) / sizeof(const char *) / 6;
728     struct changes_args args;
729     int i;
730 
731     for (i = 0; i < num_changes; i++)
732       {
733         change_t change;
734 
735         /* Set up the current change item. */
736         change.path = bogus_changes[i][1];
737         change.noderev_id = svn_fs_parse_id(bogus_changes[i][2],
738                                             strlen(bogus_changes[i][2]),
739                                             subpool);
740         change.kind = string_to_kind(bogus_changes[i][3]);
741         change.text_mod = bogus_changes[i][4] ? 1 : 0;
742         change.prop_mod = bogus_changes[i][5] ? 1 : 0;
743 
744         /* Set up transaction baton. */
745         args.fs = fs;
746         args.key = "x";
747         args.change = &change;
748 
749         /* Write new changes to the changes table. */
750         SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_add, &args,
751                                        TRUE, subpool));
752       }
753 
754     /* Now read 'em back, looking for an error. */
755     args.fs = fs;
756     args.key = "x";
757     err = svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch, &args,
758                                  TRUE, subpool);
759     if (!err)
760       {
761         return svn_error_create(SVN_ERR_TEST_FAILED, 0,
762                                 "Expected SVN_ERR_FS_CORRUPT, got no error.");
763       }
764     else if (err->apr_err != SVN_ERR_FS_CORRUPT)
765       {
766         return svn_error_create(SVN_ERR_TEST_FAILED, err,
767                                 "Expected SVN_ERR_FS_CORRUPT, got a different error.");
768       }
769     else
770       {
771         svn_error_clear(err);
772       }
773 
774     /* Post-test cleanup. */
775     SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_delete, &args,
776                                    TRUE, subpool));
777   }
778 
779   /* Test changes bogus because there's a change other than an
780      add-type changes on a deleted path. */
781   svn_pool_clear(subpool);
782   {
783     static const char *bogus_changes[][6]
784          /* KEY   PATH   NODEREVID  KIND       TEXT PROP */
785       = { { "x",  "/foo",  "1.0.0",  "delete",  0 ,  0  },
786           { "x",  "/foo",  "1.0.0",  "modify", "1",  0  } };
787     int num_changes = sizeof(bogus_changes) / sizeof(const char *) / 6;
788     struct changes_args args;
789     int i;
790 
791     for (i = 0; i < num_changes; i++)
792       {
793         change_t change;
794 
795         /* Set up the current change item. */
796         change.path = bogus_changes[i][1];
797         change.noderev_id = svn_fs_parse_id(bogus_changes[i][2],
798                                             strlen(bogus_changes[i][2]),
799                                             subpool);
800         change.kind = string_to_kind(bogus_changes[i][3]);
801         change.text_mod = bogus_changes[i][4] ? 1 : 0;
802         change.prop_mod = bogus_changes[i][5] ? 1 : 0;
803 
804         /* Set up transaction baton. */
805         args.fs = fs;
806         args.key = "x";
807         args.change = &change;
808 
809         /* Write new changes to the changes table. */
810         SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_add, &args,
811                                        TRUE, subpool));
812       }
813 
814     /* Now read 'em back, looking for an error. */
815     args.fs = fs;
816     args.key = "x";
817     err = svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch, &args,
818                                  TRUE, subpool);
819     if (!err)
820       {
821         return svn_error_create(SVN_ERR_TEST_FAILED, 0,
822                                 "Expected SVN_ERR_FS_CORRUPT, got no error.");
823       }
824     else if (err->apr_err != SVN_ERR_FS_CORRUPT)
825       {
826         return svn_error_create(SVN_ERR_TEST_FAILED, err,
827                                 "Expected SVN_ERR_FS_CORRUPT, got a different error.");
828       }
829     else
830       {
831         svn_error_clear(err);
832       }
833 
834     /* Post-test cleanup. */
835     SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_delete, &args,
836                                    TRUE, subpool));
837   }
838 
839   /* Test changes bogus because there's an add on a path that's got
840      previous non-delete changes on it. */
841   svn_pool_clear(subpool);
842   {
843     static const char *bogus_changes[][6]
844          /* KEY   PATH   NODEREVID  KIND       TEXT PROP */
845       = { { "x",  "/foo",  "1.0.0",  "modify", "1",  0  },
846           { "x",  "/foo",  "1.0.0",  "add",    "1",  0  } };
847     int num_changes = sizeof(bogus_changes) / sizeof(const char *) / 6;
848     struct changes_args args;
849     int i;
850 
851     for (i = 0; i < num_changes; i++)
852       {
853         change_t change;
854 
855         /* Set up the current change item. */
856         change.path = bogus_changes[i][1];
857         change.noderev_id = svn_fs_parse_id(bogus_changes[i][2],
858                                             strlen(bogus_changes[i][2]),
859                                             subpool);
860         change.kind = string_to_kind(bogus_changes[i][3]);
861         change.text_mod = bogus_changes[i][4] ? 1 : 0;
862         change.prop_mod = bogus_changes[i][5] ? 1 : 0;
863 
864         /* Set up transaction baton. */
865         args.fs = fs;
866         args.key = "x";
867         args.change = &change;
868 
869         /* Write new changes to the changes table. */
870         SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_add, &args,
871                                        TRUE, subpool));
872       }
873 
874     /* Now read 'em back, looking for an error. */
875     args.fs = fs;
876     args.key = "x";
877     err = svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch, &args,
878                                  TRUE, subpool);
879     if (!err)
880       {
881         return svn_error_create(SVN_ERR_TEST_FAILED, 0,
882                                 "Expected SVN_ERR_FS_CORRUPT, got no error.");
883       }
884     else if (err->apr_err != SVN_ERR_FS_CORRUPT)
885       {
886         return svn_error_create(SVN_ERR_TEST_FAILED, err,
887                                 "Expected SVN_ERR_FS_CORRUPT, got a different error.");
888       }
889     else
890       {
891         svn_error_clear(err);
892       }
893 
894     /* Post-test cleanup. */
895     SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_delete, &args,
896                                    TRUE, subpool));
897   }
898 
899   return SVN_NO_ERROR;
900 }
901 
902 
903 
904 /* The test table.  */
905 
906 static int max_threads = 4;
907 
908 static struct svn_test_descriptor_t test_funcs[] =
909   {
910     SVN_TEST_NULL,
911     SVN_TEST_OPTS_PASS(changes_add,
912                        "add changes to the changes table"),
913     SVN_TEST_OPTS_PASS(changes_fetch_raw,
914                        "fetch raw changes from the changes table"),
915     SVN_TEST_OPTS_PASS(changes_delete,
916                        "delete changes from the changes table"),
917     SVN_TEST_OPTS_PASS(changes_fetch,
918                        "fetch compressed changes from the changes table"),
919     SVN_TEST_OPTS_PASS(changes_fetch_ordering,
920                        "verify ordered-ness of fetched compressed changes"),
921     SVN_TEST_OPTS_PASS(changes_bad_sequences,
922                        "verify that bad change sequences raise errors"),
923     SVN_TEST_NULL
924   };
925 
926 SVN_TEST_MAIN
927