blob: 956649277cb0dd3f6302d0816161f3d80906ad15 [file] [log] [blame]
/* fs-helpers.c --- tests for the filesystem
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <stdlib.h>
#include <string.h>
#include <apr_pools.h>
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
#include "svn_path.h"
#include "svn_delta.h"
#include "svn_test.h"
#include "fs-helpers.h"
#include "../libsvn_fs/fs.h"
#include "../libsvn_fs/dag.h"
#include "../libsvn_fs/rev-table.h"
#include "../libsvn_fs/nodes-table.h"
#include "../libsvn_fs/trail.h"
/*-------------------------------------------------------------------*/
/** Helper routines. **/
/* Generic Berkeley DB error handler function. */
static void
berkeley_error_handler (const char *errpfx,
char *msg)
{
fprintf (stderr, "%s%s\n", errpfx ? errpfx : "", msg);
}
svn_error_t *
svn_test__fs_new (svn_fs_t **fs_p, apr_pool_t *pool)
{
*fs_p = svn_fs_new (pool);
if (! *fs_p)
return svn_error_create (SVN_ERR_FS_GENERAL, 0, NULL, pool,
"Couldn't alloc a new fs object.");
/* Provide a warning function that just dumps the message to stderr. */
svn_fs_set_warning_func (*fs_p, svn_handle_warning, 0);
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__create_fs (svn_fs_t **fs_p,
const char *name,
apr_pool_t *pool)
{
apr_finfo_t finfo;
/* If there's already a repository named NAME, delete it. Doing
things this way means that repositories stick around after a
failure for postmortem analysis, but also that tests can be
re-run without cleaning out the repositories created by prior
runs. */
if (apr_stat (&finfo, name, APR_FINFO_TYPE, pool) == APR_SUCCESS)
{
if (finfo.filetype == APR_DIR)
SVN_ERR (svn_fs_delete_berkeley (name, pool));
else
return svn_error_createf (SVN_ERR_TEST_FAILED, 0, NULL, pool,
"there is already a file named `%s'", name);
}
SVN_ERR (svn_test__fs_new (fs_p, pool));
SVN_ERR (svn_fs_create_berkeley (*fs_p, name));
/* Provide a handler for Berkeley DB error messages. */
SVN_ERR (svn_fs_set_berkeley_errcall (*fs_p, berkeley_error_handler));
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__create_repos (svn_repos_t **repos_p,
const char *name,
apr_pool_t *pool)
{
apr_finfo_t finfo;
/* If there's already a repository named NAME, delete it. Doing
things this way means that repositories stick around after a
failure for postmortem analysis, but also that tests can be
re-run without cleaning out the repositories created by prior
runs. */
if (apr_stat (&finfo, name, APR_FINFO_TYPE, pool) == APR_SUCCESS)
{
if (finfo.filetype == APR_DIR)
SVN_ERR (svn_repos_delete (name, pool));
else
return svn_error_createf (SVN_ERR_TEST_FAILED, 0, NULL, pool,
"there is already a file named `%s'", name);
}
SVN_ERR (svn_repos_create (repos_p, name, pool));
/* Provide a handler for Berkeley DB error messages. */
SVN_ERR (svn_fs_set_berkeley_errcall (svn_repos_fs (*repos_p),
berkeley_error_handler));
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__stream_to_string (svn_stringbuf_t **string,
svn_stream_t *stream,
apr_pool_t *pool)
{
char buf[10]; /* Making this really small because a) hey, they're
just tests, not the prime place to beg for
optimization, and b) we've had repository
problems in the past that only showed up when
reading a file into a buffer that couldn't hold the
file's whole contents -- the kind of thing you'd
like to catch while testing.
### cmpilato todo: Perhaps some day this size can
be passed in as a parameter. Not high on my list
of priorities today, though. */
apr_size_t len;
svn_stringbuf_t *str = svn_stringbuf_create ("", pool);
do
{
/* "please read 40 bytes into buf" */
len = sizeof (buf);
SVN_ERR (svn_stream_read (stream, buf, &len));
/* Now copy however many bytes were *actually* read into str. */
svn_stringbuf_appendbytes (str, buf, len);
} while (len); /* Continue until we're told that no bytes were
read. */
*string = str;
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__set_file_contents (svn_fs_root_t *root,
const char *path,
const char *contents,
apr_pool_t *pool)
{
svn_txdelta_window_handler_t consumer_func;
void *consumer_baton;
svn_string_t string;
SVN_ERR (svn_fs_apply_textdelta (&consumer_func, &consumer_baton,
root, path, pool));
string.data = contents;
string.len = strlen(contents);
SVN_ERR (svn_txdelta_send_string (&string, consumer_func,
consumer_baton, pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__get_file_contents (svn_fs_root_t *root,
const char *path,
svn_stringbuf_t **str,
apr_pool_t *pool)
{
svn_stream_t *stream;
SVN_ERR (svn_fs_file_contents (&stream, root, path, pool));
SVN_ERR (svn_test__stream_to_string (str, stream, pool));
return SVN_NO_ERROR;
}
/* Read all the entries in directory PATH under transaction or
revision root ROOT, copying their full paths into the TREE_ENTRIES
hash, and recursing when those entries are directories */
static svn_error_t *
get_dir_entries (apr_hash_t *tree_entries,
svn_fs_root_t *root,
svn_stringbuf_t *path,
apr_pool_t *pool)
{
apr_hash_t *entries;
apr_hash_index_t *hi;
SVN_ERR (svn_fs_dir_entries (&entries, root, path->data, pool));
/* Copy this list to the master list with the path prepended to the
names */
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
svn_fs_dirent_t *dirent;
svn_stringbuf_t *full_path;
int is_dir;
apr_hash_this (hi, &key, &keylen, &val);
dirent = val;
/* Calculate the full path of this entry (by appending the name
to the path thus far) */
full_path = svn_stringbuf_dup (path, pool);
svn_path_add_component (full_path,
svn_stringbuf_create (dirent->name, pool));
/* Now, copy this dirent to the master hash, but this time, use
the full path for the key */
apr_hash_set (tree_entries, full_path->data,
APR_HASH_KEY_STRING, dirent);
/* If this entry is a directory, recurse into the tree. */
SVN_ERR (svn_fs_is_dir (&is_dir, root, full_path->data, pool));
if (is_dir)
SVN_ERR (get_dir_entries (tree_entries, root, full_path, pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
validate_tree_entry (svn_fs_root_t *root,
const char *path,
const char *contents,
apr_pool_t *pool)
{
svn_stream_t *rstream;
svn_stringbuf_t *rstring;
int is_dir;
/* Verify that this is the expected type of node */
SVN_ERR (svn_fs_is_dir (&is_dir, root, path, pool));
if ((!is_dir && !contents) || (is_dir && contents))
return svn_error_createf
(SVN_ERR_FS_GENERAL, 0, NULL, pool,
"node `%s' in tree was of unexpected node type",
path);
/* Verify that the contents are as expected (files only) */
if (! is_dir)
{
SVN_ERR (svn_fs_file_contents (&rstream, root, path, pool));
SVN_ERR (svn_test__stream_to_string (&rstring, rstream, pool));
if (! svn_stringbuf_compare (rstring,
svn_stringbuf_create (contents, pool)))
return svn_error_createf
(SVN_ERR_FS_GENERAL, 0, NULL, pool,
"node `%s' in tree had unexpected contents",
path);
}
return SVN_NO_ERROR;
}
/* Given a transaction or revision root (ROOT), check to see if the
tree that grows from that root has all the path entries, and only
those entries, passed in the array ENTRIES (which is an array of
NUM_ENTRIES tree_test_entry_t's) */
svn_error_t *
svn_test__validate_tree (svn_fs_root_t *root,
svn_test__tree_entry_t *entries,
int num_entries,
apr_pool_t *pool)
{
apr_hash_t *tree_entries, *expected_entries;
apr_pool_t *subpool = svn_pool_create (pool);
svn_stringbuf_t *root_dir = svn_stringbuf_create ("", subpool);
svn_stringbuf_t *extra_entries = NULL;
svn_stringbuf_t *missing_entries = NULL;
svn_stringbuf_t *corrupt_entries = NULL;
apr_hash_index_t *hi;
int i;
/* Create a hash for storing our expected entries */
expected_entries = apr_hash_make (subpool);
/* Copy our array of expected entries into a hash. */
for (i = 0; i < num_entries; i++)
apr_hash_set (expected_entries, entries[i].path,
APR_HASH_KEY_STRING, &(entries[i]));
/* Create our master hash for storing the entries */
tree_entries = apr_hash_make (pool);
/* Begin the recursive directory entry dig */
SVN_ERR (get_dir_entries (tree_entries, root, root_dir, subpool));
/* For each entry in our EXPECTED_ENTRIES hash, try to find that
entry in the TREE_ENTRIES hash given us by the FS. If we find
that object, remove it from the TREE_ENTRIES. If we don't find
it, there's a problem to report! */
for (hi = apr_hash_first (subpool, expected_entries);
hi;
hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
svn_test__tree_entry_t *entry;
apr_hash_this (hi, &key, &keylen, &val);
entry = val;
/* Verify that the entry exists in our full list of entries. */
val = apr_hash_get (tree_entries, key, keylen);
if (val)
{
if (validate_tree_entry (root, entry->path,
entry->contents, subpool))
{
/* If we don't have a corrupt entries string, make one. */
if (! corrupt_entries)
corrupt_entries = svn_stringbuf_create ("", subpool);
/* Append this entry name to the list of corrupt entries. */
svn_stringbuf_appendcstr (corrupt_entries, " ");
svn_stringbuf_appendbytes (corrupt_entries, (char *)key, keylen);
svn_stringbuf_appendcstr (corrupt_entries, "\n");
}
apr_hash_set (tree_entries, key, keylen, NULL);
}
else
{
/* If we don't have a missing entries string, make one. */
if (! missing_entries)
missing_entries = svn_stringbuf_create ("", subpool);
/* Append this entry name to the list of missing entries. */
svn_stringbuf_appendcstr (missing_entries, " ");
svn_stringbuf_appendbytes (missing_entries, (char *)key, keylen);
svn_stringbuf_appendcstr (missing_entries, "\n");
}
}
/* Any entries still left in TREE_ENTRIES are extra ones that are
not expected to be present. Assemble a string with their names. */
for (hi = apr_hash_first (subpool, tree_entries);
hi;
hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t keylen;
void *val;
apr_hash_this (hi, &key, &keylen, &val);
/* If we don't have an extra entries string, make one. */
if (! extra_entries)
extra_entries = svn_stringbuf_create ("", subpool);
/* Append this entry name to the list of missing entries. */
svn_stringbuf_appendcstr (extra_entries, " ");
svn_stringbuf_appendbytes (extra_entries, (char *)key, keylen);
svn_stringbuf_appendcstr (extra_entries, "\n");
}
if (missing_entries || extra_entries || corrupt_entries)
{
return svn_error_createf
(SVN_ERR_FS_GENERAL, 0, NULL, pool,
"Repository tree does not look as expected.\n"
"Corrupt entries:\n%s"
"Missing entries:\n%s"
"Extra entries:\n%s",
corrupt_entries ? corrupt_entries->data : "",
missing_entries ? missing_entries->data : "",
extra_entries ? extra_entries->data : "");
}
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__txn_script_exec (svn_fs_root_t *txn_root,
svn_test__txn_script_command_t *script,
int num_edits,
apr_pool_t *pool)
{
int i;
/* Run through the list of edits, making the appropriate edit on
that entry in the TXN_ROOT. */
for (i = 0; i < num_edits; i++)
{
const char *path = script[i].path;
const char *param1 = script[i].param1;
int cmd = script[i].cmd;
int is_dir = (param1 == 0);
switch (cmd)
{
case 'a':
if (is_dir)
{
SVN_ERR (svn_fs_make_dir (txn_root, path, pool));
}
else
{
SVN_ERR (svn_fs_make_file (txn_root, path, pool));
SVN_ERR (svn_test__set_file_contents (txn_root, path,
param1, pool));
}
break;
case 'c':
{
svn_revnum_t youngest;
svn_fs_root_t *rev_root;
svn_fs_t *fs = svn_fs_root_fs (txn_root);
SVN_ERR (svn_fs_youngest_rev (&youngest, fs, pool));
SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest, pool));
SVN_ERR (svn_fs_copy (rev_root, path, txn_root, param1, pool));
}
break;
case 'd':
SVN_ERR (svn_fs_delete_tree (txn_root, path, pool));
break;
case 'e':
if (! is_dir)
{
SVN_ERR (svn_test__set_file_contents (txn_root, path,
param1, pool));
}
break;
default:
break;
}
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__check_greek_tree (svn_fs_root_t *root,
apr_pool_t *pool)
{
svn_stream_t *rstream;
svn_stringbuf_t *rstring;
svn_stringbuf_t *content;
int i;
const char *file_contents[12][2] =
{
{ "iota", "This is the file 'iota'.\n" },
{ "A/mu", "This is the file 'mu'.\n" },
{ "A/B/lambda", "This is the file 'lambda'.\n" },
{ "A/B/E/alpha", "This is the file 'alpha'.\n" },
{ "A/B/E/beta", "This is the file 'beta'.\n" },
{ "A/D/gamma", "This is the file 'gamma'.\n" },
{ "A/D/G/pi", "This is the file 'pi'.\n" },
{ "A/D/G/rho", "This is the file 'rho'.\n" },
{ "A/D/G/tau", "This is the file 'tau'.\n" },
{ "A/D/H/chi", "This is the file 'chi'.\n" },
{ "A/D/H/psi", "This is the file 'psi'.\n" },
{ "A/D/H/omega", "This is the file 'omega'.\n" }
};
/* Loop through the list of files, checking for matching content. */
for (i = 0; i < 12; i++)
{
SVN_ERR (svn_fs_file_contents (&rstream, root,
file_contents[i][0], pool));
SVN_ERR (svn_test__stream_to_string (&rstring, rstream, pool));
content = svn_stringbuf_create (file_contents[i][1], pool);
if (! svn_stringbuf_compare (rstring, content))
return svn_error_createf (SVN_ERR_FS_GENERAL, 0, NULL, pool,
"data read != data written in file `%s'.",
file_contents[i][0]);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_test__create_greek_tree (svn_fs_root_t *txn_root,
apr_pool_t *pool)
{
SVN_ERR (svn_fs_make_file (txn_root, "iota", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "iota", "This is the file 'iota'.\n", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/mu", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/mu", "This is the file 'mu'.\n", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A/B", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/B/lambda", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/B/lambda", "This is the file 'lambda'.\n", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A/B/E", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/B/E/alpha", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/B/E/alpha", "This is the file 'alpha'.\n", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/B/E/beta", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/B/E/beta", "This is the file 'beta'.\n", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A/B/F", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A/C", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A/D", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/D/gamma", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/D/gamma", "This is the file 'gamma'.\n", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A/D/G", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/D/G/pi", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/D/G/pi", "This is the file 'pi'.\n", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/D/G/rho", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/D/G/rho", "This is the file 'rho'.\n", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/D/G/tau", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/D/G/tau", "This is the file 'tau'.\n", pool));
SVN_ERR (svn_fs_make_dir (txn_root, "A/D/H", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/D/H/chi", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/D/H/chi", "This is the file 'chi'.\n", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/D/H/psi", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/D/H/psi", "This is the file 'psi'.\n", pool));
SVN_ERR (svn_fs_make_file (txn_root, "A/D/H/omega", pool));
SVN_ERR (svn_test__set_file_contents
(txn_root, "A/D/H/omega", "This is the file 'omega'.\n", pool));
return SVN_NO_ERROR;
}