1 /* fs-helpers.c --- tests for the filesystem
3 * ====================================================================
4 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
20 #include <apr_pools.h>
21 #include "svn_pools.h"
22 #include "svn_error.h"
25 #include "svn_delta.h"
28 #include "svn_test_fs.h"
31 /*-------------------------------------------------------------------*/
33 /** Helper routines. **/
37 fs_warning_handler(void *baton
, svn_error_t
*err
)
39 svn_handle_warning(stderr
, err
);
42 /* This is used only by bdb fs tests. */
44 svn_test__fs_new(svn_fs_t
**fs_p
, apr_pool_t
*pool
)
46 apr_hash_t
*fs_config
= apr_hash_make(pool
);
47 apr_hash_set(fs_config
, SVN_FS_CONFIG_BDB_TXN_NOSYNC
,
48 APR_HASH_KEY_STRING
, "1");
50 *fs_p
= svn_fs_new(fs_config
, pool
);
52 return svn_error_create(SVN_ERR_FS_GENERAL
, NULL
,
53 "Couldn't alloc a new fs object.");
55 /* Provide a warning function that just dumps the message to stderr. */
56 svn_fs_set_warning_func(*fs_p
, fs_warning_handler
, NULL
);
63 make_fs_config(const char *fs_type
,
66 apr_hash_t
*fs_config
= apr_hash_make(pool
);
67 apr_hash_set(fs_config
, SVN_FS_CONFIG_BDB_TXN_NOSYNC
,
68 APR_HASH_KEY_STRING
, "1");
69 apr_hash_set(fs_config
, SVN_FS_CONFIG_FS_TYPE
,
77 svn_test__create_fs(svn_fs_t
**fs_p
,
83 apr_hash_t
*fs_config
= make_fs_config(fs_type
, pool
);
85 /* If there's already a repository named NAME, delete it. Doing
86 things this way means that repositories stick around after a
87 failure for postmortem analysis, but also that tests can be
88 re-run without cleaning out the repositories created by prior
90 if (apr_stat(&finfo
, name
, APR_FINFO_TYPE
, pool
) == APR_SUCCESS
)
92 if (finfo
.filetype
== APR_DIR
)
93 SVN_ERR(svn_fs_delete_fs(name
, pool
));
95 return svn_error_createf(SVN_ERR_TEST_FAILED
, NULL
,
96 "there is already a file named '%s'", name
);
99 SVN_ERR(svn_fs_create(fs_p
, name
, fs_config
, pool
));
101 return svn_error_create(SVN_ERR_FS_GENERAL
, NULL
,
102 "Couldn't alloc a new fs object.");
104 /* Provide a warning function that just dumps the message to stderr. */
105 svn_fs_set_warning_func(*fs_p
, fs_warning_handler
, NULL
);
107 /* Register this fs for cleanup. */
108 svn_test_add_dir_cleanup(name
);
115 svn_test__create_repos(svn_repos_t
**repos_p
,
121 apr_hash_t
*fs_config
= make_fs_config(fs_type
, pool
);
123 /* If there's already a repository named NAME, delete it. Doing
124 things this way means that repositories stick around after a
125 failure for postmortem analysis, but also that tests can be
126 re-run without cleaning out the repositories created by prior
128 if (apr_stat(&finfo
, name
, APR_FINFO_TYPE
, pool
) == APR_SUCCESS
)
130 if (finfo
.filetype
== APR_DIR
)
131 SVN_ERR(svn_repos_delete(name
, pool
));
133 return svn_error_createf(SVN_ERR_TEST_FAILED
, NULL
,
134 "there is already a file named '%s'", name
);
137 SVN_ERR(svn_repos_create(repos_p
, name
, NULL
, NULL
, NULL
,
140 /* Register this repo for cleanup. */
141 svn_test_add_dir_cleanup(name
);
148 svn_test__stream_to_string(svn_stringbuf_t
**string
,
149 svn_stream_t
*stream
,
152 char buf
[10]; /* Making this really small because a) hey, they're
153 just tests, not the prime place to beg for
154 optimization, and b) we've had repository
155 problems in the past that only showed up when
156 reading a file into a buffer that couldn't hold the
157 file's whole contents -- the kind of thing you'd
158 like to catch while testing.
160 ### cmpilato todo: Perhaps some day this size can
161 be passed in as a parameter. Not high on my list
162 of priorities today, though. */
165 svn_stringbuf_t
*str
= svn_stringbuf_create("", pool
);
170 SVN_ERR(svn_stream_read(stream
, buf
, &len
));
172 /* Now copy however many bytes were *actually* read into str. */
173 svn_stringbuf_appendbytes(str
, buf
, len
);
175 } while (len
); /* Continue until we're told that no bytes were
183 svn_test__set_file_contents(svn_fs_root_t
*root
,
185 const char *contents
,
188 svn_txdelta_window_handler_t consumer_func
;
189 void *consumer_baton
;
192 SVN_ERR(svn_fs_apply_textdelta(&consumer_func
, &consumer_baton
,
193 root
, path
, NULL
, NULL
, pool
));
195 string
.data
= contents
;
196 string
.len
= strlen(contents
);
197 SVN_ERR(svn_txdelta_send_string(&string
, consumer_func
,
198 consumer_baton
, pool
));
205 svn_test__get_file_contents(svn_fs_root_t
*root
,
207 svn_stringbuf_t
**str
,
210 svn_stream_t
*stream
;
212 SVN_ERR(svn_fs_file_contents(&stream
, root
, path
, pool
));
213 SVN_ERR(svn_test__stream_to_string(str
, stream
, pool
));
219 /* Read all the entries in directory PATH under transaction or
220 revision root ROOT, copying their full paths into the TREE_ENTRIES
221 hash, and recursing when those entries are directories */
223 get_dir_entries(apr_hash_t
*tree_entries
,
229 apr_hash_index_t
*hi
;
231 SVN_ERR(svn_fs_dir_entries(&entries
, root
, path
, pool
));
233 /* Copy this list to the master list with the path prepended to the
235 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
238 svn_fs_dirent_t
*dirent
;
239 const char *full_path
;
241 apr_hash_this(hi
, NULL
, NULL
, &val
);
244 /* Calculate the full path of this entry (by appending the name
245 to the path thus far) */
246 full_path
= svn_path_join(path
, dirent
->name
, pool
);
248 /* Now, copy this dirent to the master hash, but this time, use
249 the full path for the key */
250 apr_hash_set(tree_entries
, full_path
, APR_HASH_KEY_STRING
, dirent
);
252 /* If this entry is a directory, recurse into the tree. */
253 if (dirent
->kind
== svn_node_dir
)
254 SVN_ERR(get_dir_entries(tree_entries
, root
, full_path
, pool
));
262 validate_tree_entry(svn_fs_root_t
*root
,
264 const char *contents
,
267 svn_stream_t
*rstream
;
268 svn_stringbuf_t
*rstring
;
269 svn_boolean_t is_dir
;
271 /* Verify that this is the expected type of node */
272 SVN_ERR(svn_fs_is_dir(&is_dir
, root
, path
, pool
));
273 if ((!is_dir
&& !contents
) || (is_dir
&& contents
))
274 return svn_error_createf
275 (SVN_ERR_FS_GENERAL
, NULL
,
276 "node '%s' in tree was of unexpected node type",
279 /* Verify that the contents are as expected (files only) */
282 SVN_ERR(svn_fs_file_contents(&rstream
, root
, path
, pool
));
283 SVN_ERR(svn_test__stream_to_string(&rstring
, rstream
, pool
));
284 if (! svn_stringbuf_compare(rstring
,
285 svn_stringbuf_create(contents
, pool
)))
286 return svn_error_createf
287 (SVN_ERR_FS_GENERAL
, NULL
,
288 "node '%s' in tree had unexpected contents",
297 /* Given a transaction or revision root (ROOT), check to see if the
298 tree that grows from that root has all the path entries, and only
299 those entries, passed in the array ENTRIES (which is an array of
300 NUM_ENTRIES tree_test_entry_t's) */
302 svn_test__validate_tree(svn_fs_root_t
*root
,
303 svn_test__tree_entry_t
*entries
,
307 apr_hash_t
*tree_entries
, *expected_entries
;
308 apr_pool_t
*subpool
= svn_pool_create(pool
);
309 svn_stringbuf_t
*extra_entries
= NULL
;
310 svn_stringbuf_t
*missing_entries
= NULL
;
311 svn_stringbuf_t
*corrupt_entries
= NULL
;
312 apr_hash_index_t
*hi
;
315 /* Create a hash for storing our expected entries */
316 expected_entries
= apr_hash_make(subpool
);
318 /* Copy our array of expected entries into a hash. */
319 for (i
= 0; i
< num_entries
; i
++)
320 apr_hash_set(expected_entries
, entries
[i
].path
,
321 APR_HASH_KEY_STRING
, &(entries
[i
]));
323 /* Create our master hash for storing the entries */
324 tree_entries
= apr_hash_make(pool
);
326 /* Begin the recursive directory entry dig */
327 SVN_ERR(get_dir_entries(tree_entries
, root
, "", subpool
));
329 /* For each entry in our EXPECTED_ENTRIES hash, try to find that
330 entry in the TREE_ENTRIES hash given us by the FS. If we find
331 that object, remove it from the TREE_ENTRIES. If we don't find
332 it, there's a problem to report! */
333 for (hi
= apr_hash_first(subpool
, expected_entries
);
335 hi
= apr_hash_next(hi
))
340 svn_test__tree_entry_t
*entry
;
342 apr_hash_this(hi
, &key
, &keylen
, &val
);
345 /* Verify that the entry exists in our full list of entries. */
346 val
= apr_hash_get(tree_entries
, key
, keylen
);
351 if ((err
= validate_tree_entry(root
, entry
->path
,
352 entry
->contents
, subpool
)))
354 /* If we don't have a corrupt entries string, make one. */
355 if (! corrupt_entries
)
356 corrupt_entries
= svn_stringbuf_create("", subpool
);
358 /* Append this entry name to the list of corrupt entries. */
359 svn_stringbuf_appendcstr(corrupt_entries
, " ");
360 svn_stringbuf_appendbytes(corrupt_entries
, (const char *)key
,
362 svn_stringbuf_appendcstr(corrupt_entries
, "\n");
363 svn_error_clear(err
);
366 apr_hash_set(tree_entries
, key
, keylen
, NULL
);
370 /* If we don't have a missing entries string, make one. */
371 if (! missing_entries
)
372 missing_entries
= svn_stringbuf_create("", subpool
);
374 /* Append this entry name to the list of missing entries. */
375 svn_stringbuf_appendcstr(missing_entries
, " ");
376 svn_stringbuf_appendbytes(missing_entries
, (const char *)key
,
378 svn_stringbuf_appendcstr(missing_entries
, "\n");
382 /* Any entries still left in TREE_ENTRIES are extra ones that are
383 not expected to be present. Assemble a string with their names. */
384 for (hi
= apr_hash_first(subpool
, tree_entries
);
386 hi
= apr_hash_next(hi
))
391 apr_hash_this(hi
, &key
, &keylen
, NULL
);
393 /* If we don't have an extra entries string, make one. */
395 extra_entries
= svn_stringbuf_create("", subpool
);
397 /* Append this entry name to the list of missing entries. */
398 svn_stringbuf_appendcstr(extra_entries
, " ");
399 svn_stringbuf_appendbytes(extra_entries
, (const char *)key
, keylen
);
400 svn_stringbuf_appendcstr(extra_entries
, "\n");
403 if (missing_entries
|| extra_entries
|| corrupt_entries
)
405 return svn_error_createf
406 (SVN_ERR_FS_GENERAL
, NULL
,
407 "Repository tree does not look as expected.\n"
408 "Corrupt entries:\n%s"
409 "Missing entries:\n%s"
410 "Extra entries:\n%s",
411 corrupt_entries
? corrupt_entries
->data
: "",
412 missing_entries
? missing_entries
->data
: "",
413 extra_entries
? extra_entries
->data
: "");
416 svn_pool_destroy(subpool
);
422 svn_test__txn_script_exec(svn_fs_root_t
*txn_root
,
423 svn_test__txn_script_command_t
*script
,
429 /* Run through the list of edits, making the appropriate edit on
430 that entry in the TXN_ROOT. */
431 for (i
= 0; i
< num_edits
; i
++)
433 const char *path
= script
[i
].path
;
434 const char *param1
= script
[i
].param1
;
435 int cmd
= script
[i
].cmd
;
436 svn_boolean_t is_dir
= (param1
== 0);
443 SVN_ERR(svn_fs_make_dir(txn_root
, path
, pool
));
447 SVN_ERR(svn_fs_make_file(txn_root
, path
, pool
));
448 SVN_ERR(svn_test__set_file_contents(txn_root
, path
,
455 svn_revnum_t youngest
;
456 svn_fs_root_t
*rev_root
;
457 svn_fs_t
*fs
= svn_fs_root_fs(txn_root
);
459 SVN_ERR(svn_fs_youngest_rev(&youngest
, fs
, pool
));
460 SVN_ERR(svn_fs_revision_root(&rev_root
, fs
, youngest
, pool
));
461 SVN_ERR(svn_fs_copy(rev_root
, path
, txn_root
, param1
, pool
));
466 SVN_ERR(svn_fs_delete(txn_root
, path
, pool
));
472 SVN_ERR(svn_test__set_file_contents(txn_root
, path
,
487 svn_test__check_greek_tree(svn_fs_root_t
*root
,
490 svn_stream_t
*rstream
;
491 svn_stringbuf_t
*rstring
;
492 svn_stringbuf_t
*content
;
495 const char *file_contents
[12][2] =
497 { "iota", "This is the file 'iota'.\n" },
498 { "A/mu", "This is the file 'mu'.\n" },
499 { "A/B/lambda", "This is the file 'lambda'.\n" },
500 { "A/B/E/alpha", "This is the file 'alpha'.\n" },
501 { "A/B/E/beta", "This is the file 'beta'.\n" },
502 { "A/D/gamma", "This is the file 'gamma'.\n" },
503 { "A/D/G/pi", "This is the file 'pi'.\n" },
504 { "A/D/G/rho", "This is the file 'rho'.\n" },
505 { "A/D/G/tau", "This is the file 'tau'.\n" },
506 { "A/D/H/chi", "This is the file 'chi'.\n" },
507 { "A/D/H/psi", "This is the file 'psi'.\n" },
508 { "A/D/H/omega", "This is the file 'omega'.\n" }
511 /* Loop through the list of files, checking for matching content. */
512 for (i
= 0; i
< 12; i
++)
514 SVN_ERR(svn_fs_file_contents(&rstream
, root
,
515 file_contents
[i
][0], pool
));
516 SVN_ERR(svn_test__stream_to_string(&rstring
, rstream
, pool
));
517 content
= svn_stringbuf_create(file_contents
[i
][1], pool
);
518 if (! svn_stringbuf_compare(rstring
, content
))
519 return svn_error_createf(SVN_ERR_FS_GENERAL
, NULL
,
520 "data read != data written in file '%s'.",
521 file_contents
[i
][0]);
529 svn_test__create_greek_tree(svn_fs_root_t
*txn_root
,
532 SVN_ERR(svn_fs_make_file(txn_root
, "iota", pool
));
533 SVN_ERR(svn_test__set_file_contents
534 (txn_root
, "iota", "This is the file 'iota'.\n", pool
));
535 SVN_ERR(svn_fs_make_dir (txn_root
, "A", pool
));
536 SVN_ERR(svn_fs_make_file(txn_root
, "A/mu", pool
));
537 SVN_ERR(svn_test__set_file_contents
538 (txn_root
, "A/mu", "This is the file 'mu'.\n", pool
));
539 SVN_ERR(svn_fs_make_dir (txn_root
, "A/B", pool
));
540 SVN_ERR(svn_fs_make_file(txn_root
, "A/B/lambda", pool
));
541 SVN_ERR(svn_test__set_file_contents
542 (txn_root
, "A/B/lambda", "This is the file 'lambda'.\n", pool
));
543 SVN_ERR(svn_fs_make_dir (txn_root
, "A/B/E", pool
));
544 SVN_ERR(svn_fs_make_file(txn_root
, "A/B/E/alpha", pool
));
545 SVN_ERR(svn_test__set_file_contents
546 (txn_root
, "A/B/E/alpha", "This is the file 'alpha'.\n", pool
));
547 SVN_ERR(svn_fs_make_file(txn_root
, "A/B/E/beta", pool
));
548 SVN_ERR(svn_test__set_file_contents
549 (txn_root
, "A/B/E/beta", "This is the file 'beta'.\n", pool
));
550 SVN_ERR(svn_fs_make_dir (txn_root
, "A/B/F", pool
));
551 SVN_ERR(svn_fs_make_dir (txn_root
, "A/C", pool
));
552 SVN_ERR(svn_fs_make_dir (txn_root
, "A/D", pool
));
553 SVN_ERR(svn_fs_make_file(txn_root
, "A/D/gamma", pool
));
554 SVN_ERR(svn_test__set_file_contents
555 (txn_root
, "A/D/gamma", "This is the file 'gamma'.\n", pool
));
556 SVN_ERR(svn_fs_make_dir (txn_root
, "A/D/G", pool
));
557 SVN_ERR(svn_fs_make_file(txn_root
, "A/D/G/pi", pool
));
558 SVN_ERR(svn_test__set_file_contents
559 (txn_root
, "A/D/G/pi", "This is the file 'pi'.\n", pool
));
560 SVN_ERR(svn_fs_make_file(txn_root
, "A/D/G/rho", pool
));
561 SVN_ERR(svn_test__set_file_contents
562 (txn_root
, "A/D/G/rho", "This is the file 'rho'.\n", pool
));
563 SVN_ERR(svn_fs_make_file(txn_root
, "A/D/G/tau", pool
));
564 SVN_ERR(svn_test__set_file_contents
565 (txn_root
, "A/D/G/tau", "This is the file 'tau'.\n", pool
));
566 SVN_ERR(svn_fs_make_dir (txn_root
, "A/D/H", pool
));
567 SVN_ERR(svn_fs_make_file(txn_root
, "A/D/H/chi", pool
));
568 SVN_ERR(svn_test__set_file_contents
569 (txn_root
, "A/D/H/chi", "This is the file 'chi'.\n", pool
));
570 SVN_ERR(svn_fs_make_file(txn_root
, "A/D/H/psi", pool
));
571 SVN_ERR(svn_test__set_file_contents
572 (txn_root
, "A/D/H/psi", "This is the file 'psi'.\n", pool
));
573 SVN_ERR(svn_fs_make_file(txn_root
, "A/D/H/omega", pool
));
574 SVN_ERR(svn_test__set_file_contents
575 (txn_root
, "A/D/H/omega", "This is the file 'omega'.\n", pool
));