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