diff options
Diffstat (limited to 'subversion/tests/libsvn_repos/repos-test.c')
-rw-r--r-- | subversion/tests/libsvn_repos/repos-test.c | 1701 |
1 files changed, 1356 insertions, 345 deletions
diff --git a/subversion/tests/libsvn_repos/repos-test.c b/subversion/tests/libsvn_repos/repos-test.c index 76687cd..87265f0 100644 --- a/subversion/tests/libsvn_repos/repos-test.c +++ b/subversion/tests/libsvn_repos/repos-test.c @@ -29,16 +29,20 @@ #include "svn_pools.h" #include "svn_error.h" #include "svn_fs.h" +#include "svn_hash.h" #include "svn_repos.h" #include "svn_path.h" #include "svn_delta.h" #include "svn_config.h" #include "svn_props.h" +#include "svn_version.h" #include "../svn_test_fs.h" #include "dir-delta-editor.h" +#include "private/svn_repos_private.h" + /* Used to terminate lines in large multi-line string literals. */ #define NL APR_EOL_STR @@ -49,6 +53,20 @@ #define MIN(a,b) (((a)<(b))?(a):(b)) #endif +/* Compare strings, like strcmp but either or both may be NULL which + * compares equal to NULL and not equal to any non-NULL string. */ +static int +strcmp_null(const char *s1, const char *s2) +{ + if (s1 && s2) + return strcmp(s1, s2); + else if (s1 || s2) + return 1; + else + return 0; +} + + static svn_error_t * dir_deltas(const svn_test_opts_t *opts, @@ -1077,9 +1095,10 @@ rmlocks(const svn_test_opts_t *opts, SVN_ERR(create_rmlocks_editor(&editor, &edit_baton, &removed, subpool)); /* Report what we have. */ - SVN_ERR(svn_repos_begin_report2(&report_baton, 1, repos, "/", "", NULL, + SVN_ERR(svn_repos_begin_report3(&report_baton, 1, repos, "/", "", NULL, FALSE, svn_depth_infinity, FALSE, FALSE, - editor, edit_baton, NULL, NULL, subpool)); + editor, edit_baton, NULL, NULL, 1024, + subpool)); SVN_ERR(svn_repos_set_path3(report_baton, "", 1, svn_depth_infinity, FALSE, NULL, subpool)); @@ -1108,30 +1127,104 @@ rmlocks(const svn_test_opts_t *opts, /* Helper for the authz test. Set *AUTHZ_P to a representation of - AUTHZ_CONTENTS, using POOL for temporary allocation. */ + AUTHZ_CONTENTS, using POOL for temporary allocation. If DISK + is TRUE then write the contents to a temp file and use + svn_repos_authz_read() to get the data if FALSE write the + data to a buffered stream and use svn_repos_authz_parse(). */ static svn_error_t * authz_get_handle(svn_authz_t **authz_p, const char *authz_contents, - apr_pool_t *pool) + svn_boolean_t disk, apr_pool_t *pool) { - const char *authz_file_path; + if (disk) + { + const char *authz_file_path; + + /* Create a temporary file. */ + SVN_ERR_W(svn_io_write_unique(&authz_file_path, NULL, + authz_contents, strlen(authz_contents), + svn_io_file_del_on_pool_cleanup, pool), + "Writing temporary authz file"); - /* Create a temporary file. */ - SVN_ERR_W(svn_io_write_unique(&authz_file_path, NULL, - authz_contents, strlen(authz_contents), - svn_io_file_del_on_pool_cleanup, pool), - "Writing temporary authz file"); + /* Read the authz configuration back and start testing. */ + SVN_ERR_W(svn_repos_authz_read(authz_p, authz_file_path, TRUE, pool), + "Opening test authz file"); - /* Read the authz configuration back and start testing. */ - SVN_ERR_W(svn_repos_authz_read(authz_p, authz_file_path, TRUE, pool), - "Opening test authz file"); + /* Done with the file. */ + SVN_ERR_W(svn_io_remove_file(authz_file_path, pool), + "Removing test authz file"); + } + else + { + svn_stream_t *stream; - /* Done with the file. */ - SVN_ERR_W(svn_io_remove_file(authz_file_path, pool), - "Removing test authz file"); + stream = svn_stream_buffered(pool); + SVN_ERR_W(svn_stream_puts(stream, authz_contents), + "Writing authz contents to stream"); + + SVN_ERR_W(svn_repos_authz_parse(authz_p, stream, NULL, pool), + "Parsing the authz contents"); + + SVN_ERR_W(svn_stream_close(stream), + "Closing the stream"); + } return SVN_NO_ERROR; } +struct check_access_tests { + const char *path; + const char *repo_name; + const char *user; + const svn_repos_authz_access_t required; + const svn_boolean_t expected; +}; + +/* Helper for the authz test. Runs a set of tests against AUTHZ_CFG + * as defined in TESTS. */ +static svn_error_t * +authz_check_access(svn_authz_t *authz_cfg, + const struct check_access_tests *tests, + apr_pool_t *pool) +{ + int i; + svn_boolean_t access_granted; + + /* Loop over the test array and test each case. */ + for (i = 0; !(tests[i].path == NULL + && tests[i].required == svn_authz_none); i++) + { + SVN_ERR(svn_repos_authz_check_access(authz_cfg, + tests[i].repo_name, + tests[i].path, + tests[i].user, + tests[i].required, + &access_granted, pool)); + + if (access_granted != tests[i].expected) + { + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Authz incorrectly %s %s%s access " + "to %s%s%s for user %s", + access_granted ? + "grants" : "denies", + tests[i].required + & svn_authz_recursive ? + "recursive " : "", + tests[i].required + & svn_authz_read ? + "read" : "write", + tests[i].repo_name ? + tests[i].repo_name : "", + tests[i].repo_name ? + ":" : "", + tests[i].path, + tests[i].user ? + tests[i].user : "-"); + } + } + + return SVN_NO_ERROR; +} /* Test that authz is giving out the right authorizations. */ @@ -1143,36 +1236,30 @@ authz(apr_pool_t *pool) svn_error_t *err; svn_boolean_t access_granted; apr_pool_t *subpool = svn_pool_create(pool); - int i; + /* Definition of the paths to test and expected replies for each. */ - struct - { - const char *path; - const char *user; - const svn_repos_authz_access_t required; - const svn_boolean_t expected; - } test_set[] = { + struct check_access_tests test_set[] = { /* Test that read rules are correctly used. */ - { "/A", NULL, svn_authz_read, TRUE }, - { "/iota", NULL, svn_authz_read, FALSE }, + { "/A", "greek", NULL, svn_authz_read, TRUE }, + { "/iota", "greek", NULL, svn_authz_read, FALSE }, /* Test that write rules are correctly used. */ - { "/A", "plato", svn_authz_write, TRUE }, - { "/A", NULL, svn_authz_write, FALSE }, + { "/A", "greek", "plato", svn_authz_write, TRUE }, + { "/A", "greek", NULL, svn_authz_write, FALSE }, /* Test that pan-repository rules are found and used. */ - { "/A/B/lambda", "plato", svn_authz_read, TRUE }, - { "/A/B/lambda", NULL, svn_authz_read, FALSE }, + { "/A/B/lambda", "greek", "plato", svn_authz_read, TRUE }, + { "/A/B/lambda", "greek", NULL, svn_authz_read, FALSE }, /* Test that authz uses parent path ACLs if no rule for the path exists. */ - { "/A/C", NULL, svn_authz_read, TRUE }, + { "/A/C", "greek", NULL, svn_authz_read, TRUE }, /* Test that recursive access requests take into account the rules of subpaths. */ - { "/A/D", "plato", svn_authz_read | svn_authz_recursive, TRUE }, - { "/A/D", NULL, svn_authz_read | svn_authz_recursive, FALSE }, + { "/A/D", "greek", "plato", svn_authz_read | svn_authz_recursive, TRUE }, + { "/A/D", "greek", NULL, svn_authz_read | svn_authz_recursive, FALSE }, /* Test global write access lookups. */ - { NULL, "plato", svn_authz_read, TRUE }, - { NULL, NULL, svn_authz_write, FALSE }, + { NULL, "greek", "plato", svn_authz_read, TRUE }, + { NULL, "greek", NULL, svn_authz_write, FALSE }, /* Sentinel */ - { NULL, NULL, svn_authz_none, FALSE } + { NULL, NULL, NULL, svn_authz_none, FALSE } }; /* The test logic: @@ -1224,37 +1311,14 @@ authz(apr_pool_t *pool) "" NL; /* Load the test authz rules. */ - SVN_ERR(authz_get_handle(&authz_cfg, contents, subpool)); + SVN_ERR(authz_get_handle(&authz_cfg, contents, FALSE, subpool)); /* Loop over the test array and test each case. */ - for (i = 0; !(test_set[i].path == NULL - && test_set[i].required == svn_authz_none); i++) - { - SVN_ERR(svn_repos_authz_check_access(authz_cfg, "greek", - test_set[i].path, - test_set[i].user, - test_set[i].required, - &access_granted, subpool)); - - if (access_granted != test_set[i].expected) - { - return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, - "Authz incorrectly %s %s%s access " - "to greek:%s for user %s", - access_granted ? - "grants" : "denies", - test_set[i].required - & svn_authz_recursive ? - "recursive " : "", - test_set[i].required - & svn_authz_read ? - "read" : "write", - test_set[i].path, - test_set[i].user ? - test_set[i].user : "-"); - } - } + SVN_ERR(authz_check_access(authz_cfg, test_set, subpool)); + /* Repeat the previous test on disk */ + SVN_ERR(authz_get_handle(&authz_cfg, contents, TRUE, subpool)); + SVN_ERR(authz_check_access(authz_cfg, test_set, subpool)); /* The authz rules for the phase 2 tests, first case (cyclic dependency). */ @@ -1268,7 +1332,7 @@ authz(apr_pool_t *pool) /* Load the test authz rules and check that group cycles are reported. */ - err = authz_get_handle(&authz_cfg, contents, subpool); + err = authz_get_handle(&authz_cfg, contents, FALSE, subpool); if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) return svn_error_createf(SVN_ERR_TEST_FAILED, err, "Got %s error instead of expected " @@ -1283,7 +1347,7 @@ authz(apr_pool_t *pool) "@senate = r" NL; /* Check that references to undefined groups are reported. */ - err = authz_get_handle(&authz_cfg, contents, subpool); + err = authz_get_handle(&authz_cfg, contents, FALSE, subpool); if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) return svn_error_createf(SVN_ERR_TEST_FAILED, err, "Got %s error instead of expected " @@ -1300,7 +1364,7 @@ authz(apr_pool_t *pool) "* =" NL; /* Load the test authz rules. */ - SVN_ERR(authz_get_handle(&authz_cfg, contents, subpool)); + SVN_ERR(authz_get_handle(&authz_cfg, contents, FALSE, subpool)); /* Verify that the rule on /dir2/secret doesn't affect this request */ @@ -1314,12 +1378,525 @@ authz(apr_pool_t *pool) "Regression: incomplete ancestry test " "for recursive access lookup."); + /* The authz rules for the phase 4 tests */ + contents = + "[greek:/dir2//secret]" NL + "* =" NL; + SVN_TEST_ASSERT_ERROR(authz_get_handle(&authz_cfg, contents, FALSE, subpool), + SVN_ERR_AUTHZ_INVALID_CONFIG); + /* That's a wrap! */ svn_pool_destroy(subpool); return SVN_NO_ERROR; } + +/* Test in-repo authz paths */ +static svn_error_t * +in_repo_authz(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t youngest_rev; + svn_authz_t *authz_cfg; + const char *authz_contents; + const char *repos_root; + const char *repos_url; + const char *authz_url; + const char *noent_authz_url; + svn_error_t *err; + struct check_access_tests test_set[] = { + /* reads */ + { "/A", NULL, NULL, svn_authz_read, FALSE }, + { "/A", NULL, "plato", svn_authz_read, TRUE }, + { "/A", NULL, "socrates", svn_authz_read, TRUE }, + /* writes */ + { "/A", NULL, NULL, svn_authz_write, FALSE }, + { "/A", NULL, "socrates", svn_authz_write, FALSE }, + { "/A", NULL, "plato", svn_authz_write, TRUE }, + /* Sentinel */ + { NULL, NULL, NULL, svn_authz_none, FALSE } + }; + + /* Test plan: + * Create an authz file and put it in the repository. + * Verify it can be read with an relative URL. + * Verify it can be read with an absolute URL. + * Verify non-existant path does not error out when must_exist is FALSE. + * Verify non-existant path does error out when must_exist is TRUE. + * Verify that an http:// URL produces an error. + * Verify that an svn:// URL produces an error. + */ + + /* What we'll put in the authz file, it's simple since we're not testing + * the parsing, just that we got what we expected. */ + authz_contents = + "" NL + "" NL + "[/]" NL + "plato = rw" NL + "socrates = r"; + + /* Create a filesystem and repository. */ + SVN_ERR(svn_test__create_repos(&repos, "test-repo-in-repo-authz", + opts, pool)); + fs = svn_repos_fs(repos); + + /* Commit the authz file to the repo. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_file(txn_root, "authz", pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "authz", authz_contents, + pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + + repos_root = svn_repos_path(repos, pool); + SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_url, repos_root, pool)); + authz_url = apr_pstrcat(pool, repos_url, "/authz", (char *)NULL); + noent_authz_url = apr_pstrcat(pool, repos_url, "/A/authz", (char *)NULL); + + /* absolute file URL. */ + SVN_ERR(svn_repos_authz_read2(&authz_cfg, authz_url, NULL, TRUE, pool)); + SVN_ERR(authz_check_access(authz_cfg, test_set, pool)); + + /* Non-existant path in the repo with must_exist set to FALSE */ + SVN_ERR(svn_repos_authz_read2(&authz_cfg, noent_authz_url, NULL, + FALSE, pool)); + + /* Non-existant path in the repo with must_exist set to TRUE */ + err = svn_repos_authz_read2(&authz_cfg, noent_authz_url, NULL, TRUE, pool); + if (!err || err->apr_err != SVN_ERR_ILLEGAL_TARGET) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_ILLEGAL_TARGET", + err ? "unexpected" : "no"); + svn_error_clear(err); + + /* http:// URL which is unsupported */ + err = svn_repos_authz_read2(&authz_cfg, "http://example.com/repo/authz", + NULL, TRUE, pool); + if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_RA_ILLEGAL_URL", + err ? "unexpected" : "no"); + svn_error_clear(err); + + /* svn:// URL which is unsupported */ + err = svn_repos_authz_read2(&authz_cfg, "svn://example.com/repo/authz", + NULL, TRUE, pool); + if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_RA_ILLEGAL_URL", + err ? "unexpected" : "no"); + svn_error_clear(err); + + + return SVN_NO_ERROR; +} + + +/* Test in-repo authz with global groups. */ +static svn_error_t * +in_repo_groups_authz(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t youngest_rev; + svn_authz_t *authz_cfg; + const char *groups_contents; + const char *authz_contents; + const char *repos_root; + const char *repos_url; + const char *groups_url; + const char *noent_groups_url; + const char *authz_url; + const char *empty_authz_url; + const char *noent_authz_url; + svn_error_t *err; + struct check_access_tests test_set[] = { + /* reads */ + { "/A", NULL, NULL, svn_authz_read, FALSE }, + { "/A", NULL, "plato", svn_authz_read, TRUE }, + { "/A", NULL, "socrates", svn_authz_read, TRUE }, + { "/A", NULL, "solon", svn_authz_read, TRUE }, + { "/A", NULL, "ephialtes", svn_authz_read, TRUE }, + /* writes */ + { "/A", NULL, NULL, svn_authz_write, FALSE }, + { "/A", NULL, "plato", svn_authz_write, FALSE }, + { "/A", NULL, "socrates", svn_authz_write, FALSE }, + { "/A", NULL, "solon", svn_authz_write, TRUE }, + { "/A", NULL, "ephialtes", svn_authz_write, TRUE }, + /* Sentinel */ + { NULL, NULL, NULL, svn_authz_none, FALSE } + }; + + /* Test plan: + * 1. Create an authz file, a global groups file and an empty authz file, + * put all these files in the repository. The empty authz file is + * required to perform the non-existent path checks (4-7) -- + * otherwise we would get the authz validation error due to undefined + * groups. + * 2. Verify that the groups file can be read with an relative URL. + * 3. Verify that the groups file can be read with an absolute URL. + * 4. Verify that non-existent groups file path does not error out when + * must_exist is FALSE. + * 5. Same as (4), but when both authz and groups file paths do + * not exist. + * 6. Verify that non-existent path for the groups file does error out when + * must_exist is TRUE. + * 7. Verify that an http:// URL produces an error. + * 8. Verify that an svn:// URL produces an error. + */ + + /* What we'll put in the authz and groups files, it's simple since + * we're not testing the parsing, just that we got what we expected. */ + + groups_contents = + "[groups]" NL + "philosophers = plato, socrates" NL + "senate = solon, ephialtes" NL + "" NL; + + authz_contents = + "[/]" NL + "@senate = rw" NL + "@philosophers = r" NL + "" NL; + + /* Create a filesystem and repository. */ + SVN_ERR(svn_test__create_repos(&repos, + "test-repo-in-repo-global-groups-authz", + opts, pool)); + fs = svn_repos_fs(repos); + + /* Commit the authz, empty authz and groups files to the repo. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_file(txn_root, "groups", pool)); + SVN_ERR(svn_fs_make_file(txn_root, "authz", pool)); + SVN_ERR(svn_fs_make_file(txn_root, "empty-authz", pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "groups", + groups_contents, pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "authz", + authz_contents, pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "empty-authz", "", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + + /* Calculate URLs */ + repos_root = svn_repos_path(repos, pool); + SVN_ERR(svn_uri_get_file_url_from_dirent(&repos_url, repos_root, pool)); + authz_url = apr_pstrcat(pool, repos_url, "/authz", (char *)NULL); + empty_authz_url = apr_pstrcat(pool, repos_url, "/empty-authz", (char *)NULL); + noent_authz_url = apr_pstrcat(pool, repos_url, "/A/authz", (char *)NULL); + groups_url = apr_pstrcat(pool, repos_url, "/groups", (char *)NULL); + noent_groups_url = apr_pstrcat(pool, repos_url, "/A/groups", (char *)NULL); + + + /* absolute file URLs. */ + groups_url = apr_pstrcat(pool, repos_url, "/groups", (char *)NULL); + SVN_ERR(svn_repos_authz_read2(&authz_cfg, authz_url, groups_url, TRUE, pool)); + SVN_ERR(authz_check_access(authz_cfg, test_set, pool)); + + /* Non-existent path for the groups file with must_exist + * set to TRUE */ + SVN_ERR(svn_repos_authz_read2(&authz_cfg, empty_authz_url, noent_groups_url, + FALSE, pool)); + + /* Non-existent paths for both the authz and the groups files + * with must_exist set to TRUE */ + SVN_ERR(svn_repos_authz_read2(&authz_cfg, noent_authz_url, noent_groups_url, + FALSE, pool)); + + /* Non-existent path for the groups file with must_exist + * set to TRUE */ + err = svn_repos_authz_read2(&authz_cfg, empty_authz_url, noent_groups_url, + TRUE, pool); + if (!err || err->apr_err != SVN_ERR_ILLEGAL_TARGET) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_ILLEGAL_TARGET", + err ? "unexpected" : "no"); + svn_error_clear(err); + + /* http:// URL which is unsupported */ + err = svn_repos_authz_read2(&authz_cfg, empty_authz_url, + "http://example.com/repo/groups", + TRUE, pool); + if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_RA_ILLEGAL_URL", + err ? "unexpected" : "no"); + svn_error_clear(err); + + /* svn:// URL which is unsupported */ + err = svn_repos_authz_read2(&authz_cfg, empty_authz_url, + "http://example.com/repo/groups", + TRUE, pool); + if (!err || err->apr_err != SVN_ERR_RA_ILLEGAL_URL) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_RA_ILLEGAL_URL", + err ? "unexpected" : "no"); + svn_error_clear(err); + + + return SVN_NO_ERROR; +} + + +/* Helper for the groups_authz test. Set *AUTHZ_P to a representation of + AUTHZ_CONTENTS in conjuction with GROUPS_CONTENTS, using POOL for + temporary allocation. If DISK is TRUE then write the contents to + temporary files and use svn_repos_authz_read2() to get the data if FALSE + write the data to a buffered stream and use svn_repos_authz_parse(). */ +static svn_error_t * +authz_groups_get_handle(svn_authz_t **authz_p, + const char *authz_contents, + const char *groups_contents, + svn_boolean_t disk, + apr_pool_t *pool) +{ + if (disk) + { + const char *authz_file_path; + const char *groups_file_path; + + /* Create temporary files. */ + SVN_ERR_W(svn_io_write_unique(&authz_file_path, NULL, + authz_contents, + strlen(authz_contents), + svn_io_file_del_on_pool_cleanup, pool), + "Writing temporary authz file"); + SVN_ERR_W(svn_io_write_unique(&groups_file_path, NULL, + groups_contents, + strlen(groups_contents), + svn_io_file_del_on_pool_cleanup, pool), + "Writing temporary groups file"); + + /* Read the authz configuration back and start testing. */ + SVN_ERR_W(svn_repos_authz_read2(authz_p, authz_file_path, + groups_file_path, TRUE, pool), + "Opening test authz and groups files"); + + /* Done with the files. */ + SVN_ERR_W(svn_io_remove_file(authz_file_path, pool), + "Removing test authz file"); + SVN_ERR_W(svn_io_remove_file(groups_file_path, pool), + "Removing test groups file"); + } + else + { + svn_stream_t *stream; + svn_stream_t *groups_stream; + + /* Create the streams. */ + stream = svn_stream_buffered(pool); + groups_stream = svn_stream_buffered(pool); + + SVN_ERR_W(svn_stream_puts(stream, authz_contents), + "Writing authz contents to stream"); + SVN_ERR_W(svn_stream_puts(groups_stream, groups_contents), + "Writing groups contents to stream"); + + /* Read the authz configuration from the streams and start testing. */ + SVN_ERR_W(svn_repos_authz_parse(authz_p, stream, groups_stream, pool), + "Parsing the authz and groups contents"); + + /* Done with the streams. */ + SVN_ERR_W(svn_stream_close(stream), + "Closing the authz stream"); + SVN_ERR_W(svn_stream_close(groups_stream), + "Closing the groups stream"); + } + + return SVN_NO_ERROR; +} + +/* Test authz with global groups. */ +static svn_error_t * +groups_authz(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_authz_t *authz_cfg; + const char *authz_contents; + const char *groups_contents; + svn_error_t *err; + + struct check_access_tests test_set1[] = { + /* reads */ + { "/A", "greek", NULL, svn_authz_read, FALSE }, + { "/A", "greek", "plato", svn_authz_read, TRUE }, + { "/A", "greek", "demetrius", svn_authz_read, TRUE }, + { "/A", "greek", "galenos", svn_authz_read, TRUE }, + { "/A", "greek", "pamphilos", svn_authz_read, FALSE }, + /* writes */ + { "/A", "greek", NULL, svn_authz_write, FALSE }, + { "/A", "greek", "plato", svn_authz_write, TRUE }, + { "/A", "greek", "demetrius", svn_authz_write, FALSE }, + { "/A", "greek", "galenos", svn_authz_write, FALSE }, + { "/A", "greek", "pamphilos", svn_authz_write, FALSE }, + /* Sentinel */ + { NULL, NULL, NULL, svn_authz_none, FALSE } + }; + + struct check_access_tests test_set2[] = { + /* reads */ + { "/A", "greek", NULL, svn_authz_read, FALSE }, + { "/A", "greek", "socrates", svn_authz_read, FALSE }, + { "/B", "greek", NULL, svn_authz_read, FALSE}, + { "/B", "greek", "socrates", svn_authz_read, TRUE }, + /* writes */ + { "/A", "greek", NULL, svn_authz_write, FALSE }, + { "/A", "greek", "socrates", svn_authz_write, FALSE }, + { "/B", "greek", NULL, svn_authz_write, FALSE}, + { "/B", "greek", "socrates", svn_authz_write, TRUE }, + /* Sentinel */ + { NULL, NULL, NULL, svn_authz_none, FALSE } + }; + + /* Test plan: + * 1. Ensure that a simple setup with global groups and access rights in + * two separate files works as expected. + * 2. Verify that access rights written in the global groups file are + * discarded and affect nothing in authorization terms. + * 3. Verify that local groups in the authz file are prohibited in + * conjuction with global groups (and that a configuration error is + * reported in this scenario). + * 4. Ensure that group cycles in the global groups file are reported. + * + * All checks are performed twice -- for the configurations stored on disk + * and in memory. See authz_groups_get_handle. + */ + + groups_contents = + "[groups]" NL + "slaves = pamphilos,@gladiators" NL + "gladiators = demetrius,galenos" NL + "philosophers = plato" NL + "" NL; + + authz_contents = + "[greek:/A]" NL + "@slaves = " NL + "@gladiators = r" NL + "@philosophers = rw" NL + "" NL; + + SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, TRUE, pool)); + + SVN_ERR(authz_check_access(authz_cfg, test_set1, pool)); + + SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, FALSE, pool)); + + SVN_ERR(authz_check_access(authz_cfg, test_set1, pool)); + + /* Access rights in the global groups file are discarded. */ + groups_contents = + "[groups]" NL + "philosophers = socrates" NL + "" NL + "[greek:/A]" NL + "@philosophers = rw" NL + "" NL; + + authz_contents = + "[greek:/B]" NL + "@philosophers = rw" NL + "" NL; + + SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, TRUE, pool)); + + SVN_ERR(authz_check_access(authz_cfg, test_set2, pool)); + + SVN_ERR(authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, FALSE, pool)); + + SVN_ERR(authz_check_access(authz_cfg, test_set2, pool)); + + /* Local groups cannot be used in conjuction with global groups. */ + groups_contents = + "[groups]" NL + "slaves = maximus" NL + "" NL; + + authz_contents = + "[greek:/A]" NL + "@slaves = " NL + "@kings = rw" NL + "" NL + "[groups]" NL + /* That's an epic story of the slave who tried to become a king. */ + "kings = maximus" NL + "" NL; + + err = authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, TRUE, pool); + + if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_AUTHZ_INVALID_CONFIG", + err ? "unexpected" : "no"); + svn_error_clear(err); + + err = authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, FALSE, pool); + + if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_AUTHZ_INVALID_CONFIG", + err ? "unexpected" : "no"); + svn_error_clear(err); + + /* Ensure that group cycles are reported. */ + groups_contents = + "[groups]" NL + "slaves = cooks,scribes,@gladiators" NL + "gladiators = equites,thraces,@slaves" NL + "" NL; + + authz_contents = + "[greek:/A]" NL + "@slaves = r" NL + "" NL; + + err = authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, TRUE, pool); + + if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_AUTHZ_INVALID_CONFIG", + err ? "unexpected" : "no"); + svn_error_clear(err); + + err = authz_groups_get_handle(&authz_cfg, authz_contents, + groups_contents, FALSE, pool); + + if (!err || err->apr_err != SVN_ERR_AUTHZ_INVALID_CONFIG) + return svn_error_createf(SVN_ERR_TEST_FAILED, err, + "Got %s error instead of expected " + "SVN_ERR_AUTHZ_INVALID_CONFIG", + err ? "unexpected" : "no"); + svn_error_clear(err); + return SVN_NO_ERROR; +} /* Callback for the commit editor tests that relays requests to authz. */ @@ -1340,6 +1917,149 @@ commit_authz_cb(svn_repos_authz_access_t required, +enum action_t { + A_DELETE, + A_ADD_FILE, + A_ADD_DIR, + A_CHANGE_FILE_PROP +}; +struct authz_path_action_t +{ + enum action_t action; + const char *path; + svn_boolean_t authz_error_expected; + const char *copyfrom_path; +}; + +/* Return the appropriate dir baton for the parent of PATH in *DIR_BATON, + allocated in POOL. */ +static svn_error_t * +get_dir_baton(void **dir_baton, + const char *path, + const svn_delta_editor_t *editor, + void *root_baton, + apr_pool_t *pool) +{ + int i; + apr_array_header_t *path_bits = svn_path_decompose(path, pool); + const char *path_so_far = ""; + + *dir_baton = root_baton; + for (i = 0; i < (path_bits->nelts - 1); i++) + { + const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *); + path_so_far = svn_path_join(path_so_far, path_bit, pool); + SVN_ERR(editor->open_directory(path_so_far, *dir_baton, + SVN_INVALID_REVNUM, pool, dir_baton)); + } + + return SVN_NO_ERROR; +} + +/* Return the appropriate file baton for PATH in *FILE_BATON, allocated in + POOL. */ +static svn_error_t * +get_file_baton(void **file_baton, + const char *path, + const svn_delta_editor_t *editor, + void *root_baton, + apr_pool_t *pool) +{ + void *dir_baton; + + SVN_ERR(get_dir_baton(&dir_baton, path, editor, root_baton, pool)); + + SVN_ERR(editor->open_file(path, dir_baton, SVN_INVALID_REVNUM, pool, + file_baton)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_path_authz(svn_repos_t *repos, + struct authz_path_action_t *path_action, + svn_authz_t *authz_file, + svn_revnum_t youngest_rev, + apr_pool_t *scratch_pool) +{ + void *edit_baton; + void *root_baton; + void *dir_baton; + void *file_baton; + void *out_baton; + const svn_delta_editor_t *editor; + svn_error_t *err; + svn_error_t *err2; + + /* Create a new commit editor in which we're going to play with + authz */ + SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos, + NULL, "file://test", "/", + "plato", "test commit", NULL, + NULL, commit_authz_cb, authz_file, + scratch_pool)); + + /* Start fiddling. First get the root, which is readonly. */ + SVN_ERR(editor->open_root(edit_baton, 1, scratch_pool, &root_baton)); + + /* Fetch the appropriate baton for our action. This may involve opening + intermediate batons, but we only care about the final one for the + cooresponding action. */ + if (path_action->action == A_CHANGE_FILE_PROP) + SVN_ERR(get_file_baton(&file_baton, path_action->path, editor, root_baton, + scratch_pool)); + else + SVN_ERR(get_dir_baton(&dir_baton, path_action->path, editor, root_baton, + scratch_pool)); + + /* Test the appropriate action. */ + switch (path_action->action) + { + case A_DELETE: + err = editor->delete_entry(path_action->path, SVN_INVALID_REVNUM, + dir_baton, scratch_pool); + break; + + case A_CHANGE_FILE_PROP: + err = editor->change_file_prop(file_baton, "svn:test", + svn_string_create("test", scratch_pool), + scratch_pool); + break; + + case A_ADD_FILE: + err = editor->add_file(path_action->path, dir_baton, + path_action->copyfrom_path, youngest_rev, + scratch_pool, &out_baton); + break; + + case A_ADD_DIR: + err = editor->add_directory(path_action->path, dir_baton, + path_action->copyfrom_path, youngest_rev, + scratch_pool, &out_baton); + break; + + default: + SVN_TEST_ASSERT(FALSE); + } + + /* Don't worry about closing batons, just abort the edit. Since errors + may be delayed, we need to capture results of the abort as well. */ + err2 = editor->abort_edit(edit_baton, scratch_pool); + if (!err) + err = err2; + else + svn_error_clear(err2); + + /* Check for potential errors. */ + if (path_action->authz_error_expected) + SVN_TEST_ASSERT_ERROR(err, SVN_ERR_AUTHZ_UNWRITABLE); + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + + /* Test that the commit editor is taking authz into account properly */ static svn_error_t * @@ -1351,13 +2071,26 @@ commit_editor_authz(const svn_test_opts_t *opts, svn_fs_txn_t *txn; svn_fs_root_t *txn_root; svn_revnum_t youngest_rev; - void *edit_baton; - void *root_baton, *dir_baton, *dir2_baton, *file_baton; - svn_error_t *err; - const svn_delta_editor_t *editor; svn_authz_t *authz_file; - apr_pool_t *subpool = svn_pool_create(pool); + apr_pool_t *iterpool; const char *authz_contents; + int i; + struct authz_path_action_t path_actions[] = { + { A_DELETE, "/iota", TRUE }, + { A_CHANGE_FILE_PROP, "/iota", TRUE }, + { A_ADD_FILE, "/alpha", TRUE }, + { A_ADD_FILE, "/alpha", TRUE, "file://test/A/B/lambda" }, + { A_ADD_DIR, "/I", TRUE }, + { A_ADD_DIR, "/J", TRUE, "file://test/A/D" }, + { A_ADD_FILE, "/A/alpha", TRUE }, + { A_ADD_FILE, "/A/B/theta", FALSE }, + { A_DELETE, "/A/mu", FALSE }, + { A_ADD_DIR, "/A/E", FALSE }, + { A_ADD_DIR, "/A/J", FALSE, "file://test/A/D" }, + { A_DELETE, "A/D/G", TRUE }, + { A_DELETE, "A/D/H", FALSE }, + { A_CHANGE_FILE_PROP, "A/D/gamma", FALSE } + }; /* The Test Plan * @@ -1367,25 +2100,24 @@ commit_editor_authz(const svn_test_opts_t *opts, * authorized/denied when necessary. We don't try to be exhaustive * in the kinds of authz lookups. We just make sure that the editor * replies to the calls in a way that proves it is doing authz - * lookups. + * lookups. Some actions are tested implicitly (such as open_file being + * required for change_file_props). * - * Note that this use of the commit editor is not kosher according - * to the generic editor API (we aren't allowed to continue editing - * after an error, nor are we allowed to assume that errors are - * returned by the operations which caused them). But it should - * work fine with this particular editor implementation. + * Note that because of the error handling requirements of the generic + * editor API, each operation needs its own editor, which is handled by + * a helper function above. */ /* Create a filesystem and repository. */ SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-authz", - opts, subpool)); + opts, pool)); fs = svn_repos_fs(repos); /* Prepare a txn to receive the greek tree. */ - SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); - SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); - SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); - SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); /* Load the authz rules for the greek tree. */ @@ -1411,147 +2143,18 @@ commit_editor_authz(const svn_test_opts_t *opts, "[/A/D/G]" NL "plato = r"; /* No newline at end of file. */ - SVN_ERR(authz_get_handle(&authz_file, authz_contents, subpool)); - - /* Create a new commit editor in which we're going to play with - authz */ - SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos, - NULL, "file://test", "/", - "plato", "test commit", NULL, - NULL, commit_authz_cb, authz_file, - subpool)); - - /* Start fiddling. First get the root, which is readonly. All - write operations fail because of the root's permissions. */ - SVN_ERR(editor->open_root(edit_baton, 1, subpool, &root_baton)); - - /* Test denied file deletion. */ - err = editor->delete_entry("/iota", SVN_INVALID_REVNUM, root_baton, subpool); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); - - /* Test authorized file open. */ - SVN_ERR(editor->open_file("/iota", root_baton, SVN_INVALID_REVNUM, - subpool, &file_baton)); - - /* Test unauthorized file prop set. */ - err = editor->change_file_prop(file_baton, "svn:test", - svn_string_create("test", subpool), - subpool); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); - - /* Test denied file addition. */ - err = editor->add_file("/alpha", root_baton, NULL, SVN_INVALID_REVNUM, - subpool, &file_baton); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); - - /* Test denied file copy. */ - err = editor->add_file("/alpha", root_baton, "file://test/A/B/lambda", - youngest_rev, subpool, &file_baton); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); - - /* Test denied directory addition. */ - err = editor->add_directory("/I", root_baton, NULL, - SVN_INVALID_REVNUM, subpool, &dir_baton); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); + SVN_ERR(authz_get_handle(&authz_file, authz_contents, FALSE, pool)); - /* Test denied directory copy. */ - err = editor->add_directory("/J", root_baton, "file://test/A/D", - youngest_rev, subpool, &dir_baton); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); - - /* Open directory /A, to which we have read/write access. */ - SVN_ERR(editor->open_directory("/A", root_baton, - SVN_INVALID_REVNUM, - subpool, &dir_baton)); - - /* Test denied file addition. Denied because of a conflicting rule - on the file path itself. */ - err = editor->add_file("/A/alpha", dir_baton, NULL, - SVN_INVALID_REVNUM, subpool, &file_baton); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); - - /* Test authorized file addition. */ - SVN_ERR(editor->add_file("/A/B/theta", dir_baton, NULL, - SVN_INVALID_REVNUM, subpool, - &file_baton)); - - /* Test authorized file deletion. */ - SVN_ERR(editor->delete_entry("/A/mu", SVN_INVALID_REVNUM, dir_baton, - subpool)); - - /* Test authorized directory creation. */ - SVN_ERR(editor->add_directory("/A/E", dir_baton, NULL, - SVN_INVALID_REVNUM, subpool, - &dir2_baton)); - - /* Test authorized copy of a tree. */ - SVN_ERR(editor->add_directory("/A/J", dir_baton, "file://test/A/D", - youngest_rev, subpool, - &dir2_baton)); - - /* Open /A/D. This should be granted. */ - SVN_ERR(editor->open_directory("/A/D", dir_baton, SVN_INVALID_REVNUM, - subpool, &dir_baton)); - - /* Test denied recursive deletion. */ - err = editor->delete_entry("/A/D/G", SVN_INVALID_REVNUM, dir_baton, - subpool); - if (err == SVN_NO_ERROR || err->apr_err != SVN_ERR_AUTHZ_UNWRITABLE) - return svn_error_createf(SVN_ERR_TEST_FAILED, err, - "Got %s error instead of expected " - "SVN_ERR_AUTHZ_UNWRITABLE", - err ? "unexpected" : "no"); - svn_error_clear(err); - - /* Test authorized recursive deletion. */ - SVN_ERR(editor->delete_entry("/A/D/H", SVN_INVALID_REVNUM, - dir_baton, subpool)); - - /* Test authorized propset (open the file first). */ - SVN_ERR(editor->open_file("/A/D/gamma", dir_baton, SVN_INVALID_REVNUM, - subpool, &file_baton)); - SVN_ERR(editor->change_file_prop(file_baton, "svn:test", - svn_string_create("test", subpool), - subpool)); + iterpool = svn_pool_create(pool); + for (i = 0; i < (sizeof(path_actions) / sizeof(struct authz_path_action_t)); + i++) + { + svn_pool_clear(iterpool); + SVN_ERR(test_path_authz(repos, &path_actions[i], authz_file, + youngest_rev, iterpool)); + } - /* Done. */ - SVN_ERR(editor->abort_edit(edit_baton, subpool)); - svn_pool_destroy(subpool); + svn_pool_destroy(iterpool); return SVN_NO_ERROR; } @@ -1675,15 +2278,17 @@ commit_continue_txn(const svn_test_opts_t *opts, } + +/* A baton for check_location_segments(). */ struct nls_receiver_baton { int count; - svn_location_segment_t *expected_segments; + const svn_location_segment_t *expected_segments; }; - +/* Return a pretty-printed string representing SEGMENT. */ static const char * -format_segment(svn_location_segment_t *segment, +format_segment(const svn_location_segment_t *segment, apr_pool_t *pool) { return apr_psprintf(pool, "[r%ld-r%ld: /%s]", @@ -1692,14 +2297,15 @@ format_segment(svn_location_segment_t *segment, segment->path ? segment->path : "(null)"); } - +/* A location segment receiver for check_location_segments(). + * Implements svn_location_segment_receiver_t. */ static svn_error_t * nls_receiver(svn_location_segment_t *segment, void *baton, apr_pool_t *pool) { struct nls_receiver_baton *b = baton; - svn_location_segment_t *expected_segment = b->expected_segments + b->count; + const svn_location_segment_t *expected_segment = b->expected_segments + b->count; /* expected_segments->range_end can't be 0, so if we see that, it's our end-of-the-list sentry. */ @@ -1708,7 +2314,9 @@ nls_receiver(svn_location_segment_t *segment, "Got unexpected location segment: %s", format_segment(segment, pool)); - if (expected_segment->range_start != segment->range_start) + if (expected_segment->range_start != segment->range_start + || expected_segment->range_end != segment->range_end + || strcmp_null(expected_segment->path, segment->path) != 0) return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, "Location segments differ\n" " Expected location segment: %s\n" @@ -1719,18 +2327,22 @@ nls_receiver(svn_location_segment_t *segment, return SVN_NO_ERROR; } - +/* Run a svn_repos_node_location_segments() query with REPOS, PATH, PEG_REV, + * START_REV, END_REV. Check that the result exactly matches the list of + * segments EXPECTED_SEGMENTS, which is terminated by an entry with + * 'range_end'==0. + */ static svn_error_t * check_location_segments(svn_repos_t *repos, const char *path, svn_revnum_t peg_rev, svn_revnum_t start_rev, svn_revnum_t end_rev, - svn_location_segment_t *expected_segments, + const svn_location_segment_t *expected_segments, apr_pool_t *pool) { struct nls_receiver_baton b; - svn_location_segment_t *segment; + const svn_location_segment_t *segment; /* Run svn_repos_node_location_segments() with a receiver that validates against EXPECTED_SEGMENTS. */ @@ -1752,6 +2364,21 @@ check_location_segments(svn_repos_t *repos, return SVN_NO_ERROR; } +/* Inputs and expected outputs for svn_repos_node_location_segments() tests. + */ +typedef struct location_segment_test_t +{ + /* Path and peg revision to query */ + const char *path; + svn_revnum_t peg; + /* Start (youngest) and end (oldest) revisions to query */ + svn_revnum_t start; + svn_revnum_t end; + + /* Expected segments */ + svn_location_segment_t segments[10]; + +} location_segment_test_t; static svn_error_t * node_location_segments(const svn_test_opts_t *opts, @@ -1764,6 +2391,72 @@ node_location_segments(const svn_test_opts_t *opts, svn_fs_root_t *txn_root, *root; svn_revnum_t youngest_rev = 0; + static const location_segment_test_t subtests[] = + { + { /* Check locations for /@HEAD. */ + "", SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, + { + { 0, 7, "" }, + { 0 } + } + }, + { /* Check locations for A/D@HEAD. */ + "A/D", SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, + { + { 7, 7, "A/D" }, + { 3, 6, "A/D2" }, + { 1, 2, "A/D" }, + { 0 } + } + }, + { /* Check a subset of the locations for A/D@HEAD. */ + "A/D", SVN_INVALID_REVNUM, 5, 2, + { + { 3, 5, "A/D2" }, + { 2, 2, "A/D" }, + { 0 } + }, + }, + { /* Check a subset of locations for A/D2@5. */ + "A/D2", 5, 3, 2, + { + { 3, 3, "A/D2" }, + { 2, 2, "A/D" }, + { 0 } + }, + }, + { /* Check locations for A/D@6. */ + "A/D", 6, 6, SVN_INVALID_REVNUM, + { + { 1, 6, "A/D" }, + { 0 } + }, + }, + { /* Check locations for A/D/G@HEAD. */ + "A/D/G", SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, + { + { 7, 7, "A/D/G" }, + { 6, 6, "A/D2/G" }, + { 5, 5, NULL }, + { 3, 4, "A/D2/G" }, + { 1, 2, "A/D/G" }, + { 0 } + }, + }, + { /* Check a subset of the locations for A/D/G@HEAD. */ + "A/D/G", SVN_INVALID_REVNUM, 3, 2, + { + { 3, 3, "A/D2/G" }, + { 2, 2, "A/D/G" }, + { 0 } + }, + }, + { + NULL + }, + }; + const location_segment_test_t *subtest; + /* Bail (with success) on known-untestable scenarios */ if ((strcmp(opts->fs_type, "bdb") == 0) && (opts->server_minor_version == 4)) @@ -1837,117 +2530,19 @@ node_location_segments(const svn_test_opts_t *opts, SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); svn_pool_clear(subpool); - /* Check locations for /@HEAD. */ - { - svn_location_segment_t expected_segments[] = - { - { 0, 7, "" }, - { 0 } - }; - SVN_ERR(check_location_segments(repos, "", - SVN_INVALID_REVNUM, - SVN_INVALID_REVNUM, - SVN_INVALID_REVNUM, - expected_segments, pool)); - } - - /* Check locations for A/D@HEAD. */ - { - svn_location_segment_t expected_segments[] = - { - { 7, 7, "A/D" }, - { 3, 6, "A/D2" }, - { 1, 2, "A/D" }, - { 0 } - }; - SVN_ERR(check_location_segments(repos, "A/D", - SVN_INVALID_REVNUM, - SVN_INVALID_REVNUM, - SVN_INVALID_REVNUM, - expected_segments, pool)); - } - - /* Check a subset of the locations for A/D@HEAD. */ - { - svn_location_segment_t expected_segments[] = - { - { 3, 5, "A/D2" }, - { 2, 2, "A/D" }, - { 0 } - }; - SVN_ERR(check_location_segments(repos, "A/D", - SVN_INVALID_REVNUM, - 5, - 2, - expected_segments, pool)); - } - - /* Check a subset of locations for A/D2@5. */ - { - svn_location_segment_t expected_segments[] = - { - { 3, 3, "A/D2" }, - { 2, 2, "A/D" }, - { 0 } - }; - SVN_ERR(check_location_segments(repos, "A/D2", - 5, - 3, - 2, - expected_segments, pool)); - } - - /* Check locations for A/D@6. */ - { - svn_location_segment_t expected_segments[] = - { - { 1, 6, "A/D" }, - { 0 } - }; - SVN_ERR(check_location_segments(repos, "A/D", - 6, - 6, - SVN_INVALID_REVNUM, - expected_segments, pool)); - } - - /* Check locations for A/D/G@HEAD. */ - { - svn_location_segment_t expected_segments[] = - { - { 7, 7, "A/D/G" }, - { 6, 6, "A/D2/G" }, - { 5, 5, NULL }, - { 3, 4, "A/D2/G" }, - { 1, 2, "A/D2/G" }, - { 0 } - }; - SVN_ERR(check_location_segments(repos, "A/D/G", - SVN_INVALID_REVNUM, - SVN_INVALID_REVNUM, - SVN_INVALID_REVNUM, - expected_segments, pool)); - } - - /* Check a subset of the locations for A/D/G@HEAD. */ - { - svn_location_segment_t expected_segments[] = - { - { 3, 3, "A/D2/G" }, - { 2, 2, "A/D2/G" }, - { 0 } - }; - SVN_ERR(check_location_segments(repos, "A/D/G", - SVN_INVALID_REVNUM, - 3, - 2, - expected_segments, pool)); - } + /* */ + for (subtest = subtests; subtest->path; subtest++) + { + SVN_ERR(check_location_segments(repos, subtest->path, subtest->peg, + subtest->start, subtest->end, + subtest->segments, pool)); + } return SVN_NO_ERROR; } + /* Test that the reporter doesn't send deltas under excluded paths. */ static svn_error_t * reporter_depth_exclude(const svn_test_opts_t *opts, @@ -2035,9 +2630,10 @@ reporter_depth_exclude(const svn_test_opts_t *opts, SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs, txn_root, "", subpool)); - SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL, + SVN_ERR(svn_repos_begin_report3(&report_baton, 2, repos, "/", "", NULL, TRUE, svn_depth_infinity, FALSE, FALSE, - editor, edit_baton, NULL, NULL, subpool)); + editor, edit_baton, NULL, NULL, 0, + subpool)); SVN_ERR(svn_repos_set_path3(report_baton, "", 1, svn_depth_infinity, FALSE, NULL, subpool)); @@ -2092,9 +2688,10 @@ reporter_depth_exclude(const svn_test_opts_t *opts, SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs, txn_root, "", subpool)); - SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL, + SVN_ERR(svn_repos_begin_report3(&report_baton, 2, repos, "/", "", NULL, TRUE, svn_depth_infinity, FALSE, FALSE, - editor, edit_baton, NULL, NULL, subpool)); + editor, edit_baton, NULL, NULL, 0, + subpool)); SVN_ERR(svn_repos_set_path3(report_baton, "", 1, svn_depth_infinity, FALSE, NULL, subpool)); @@ -2226,7 +2823,7 @@ prop_validation(const svn_test_opts_t *opts, { svn_error_t *err; svn_repos_t *repos; - const char non_utf8_string[5] = { 'a', 0xff, 'b', '\n', 0 }; + const char non_utf8_string[5] = { 'a', '\xff', 'b', '\n', 0 }; const char *non_lf_string = "a\r\nb\n\rc\rd\n"; apr_pool_t *subpool = svn_pool_create(pool); @@ -2447,6 +3044,7 @@ test_get_file_revs(const svn_test_opts_t *opts, }; apr_hash_t *ht_trunk_results = apr_hash_make(subpool); apr_hash_t *ht_branch_results = apr_hash_make(subpool); + apr_hash_t *ht_reverse_results = apr_hash_make(subpool); for (i = 0; i < sizeof(trunk_results) / sizeof(trunk_results[0]); i++) apr_hash_set(ht_trunk_results, &trunk_results[i].rev, @@ -2456,6 +3054,11 @@ test_get_file_revs(const svn_test_opts_t *opts, apr_hash_set(ht_branch_results, &branch_results[i].rev, sizeof(svn_revnum_t), &branch_results[i]); + for (i = 0; i < sizeof(trunk_results) / sizeof(trunk_results[0]); i++) + if (!trunk_results[i].result_of_merge) + apr_hash_set(ht_reverse_results, &trunk_results[i].rev, + sizeof(svn_revnum_t), &trunk_results[i]); + /* Create the repository and verify blame results. */ SVN_ERR(svn_test__create_blame_repository(&repos, "test-repo-get-filerevs", opts, subpool)); @@ -2465,7 +3068,7 @@ test_get_file_revs(const svn_test_opts_t *opts, /* Verify blame of /trunk/A/mu */ SVN_ERR(svn_repos_get_file_revs2(repos, "/trunk/A/mu", 0, youngest_rev, - 1, NULL, NULL, + TRUE, NULL, NULL, file_rev_handler, ht_trunk_results, subpool)); @@ -2474,7 +3077,7 @@ test_get_file_revs(const svn_test_opts_t *opts, /* Verify blame of /branches/1.0.x/A/mu */ SVN_ERR(svn_repos_get_file_revs2(repos, "/branches/1.0.x/A/mu", 0, youngest_rev, - 1, NULL, NULL, + TRUE, NULL, NULL, file_rev_handler, ht_branch_results, subpool)); @@ -2482,6 +3085,13 @@ test_get_file_revs(const svn_test_opts_t *opts, /* ### TODO: Verify blame of /branches/1.0.x/A/mu in range 6-7 */ + SVN_ERR(svn_repos_get_file_revs2(repos, "/trunk/A/mu", youngest_rev, 0, + FALSE, NULL, NULL, + file_rev_handler, + ht_reverse_results, + subpool)); + SVN_TEST_ASSERT(apr_hash_count(ht_reverse_results) == 0); + svn_pool_destroy(subpool); return SVN_NO_ERROR; @@ -2501,7 +3111,7 @@ issue_4060(const svn_test_opts_t *opts, "ozymandias = r" NL "" NL; - SVN_ERR(authz_get_handle(&authz_cfg, authz_contents, subpool)); + SVN_ERR(authz_get_handle(&authz_cfg, authz_contents, FALSE, subpool)); SVN_ERR(svn_repos_authz_check_access(authz_cfg, "babylon", "/A/B/C", "ozymandias", @@ -2526,7 +3136,394 @@ issue_4060(const svn_test_opts_t *opts, return SVN_NO_ERROR; } +/* Test svn_repos_delete(). */ +static svn_error_t * +test_delete_repos(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + const char *path; + svn_node_kind_t kind; + + /* We have to use a subpool to close the svn_repos_t before calling + svn_repos_delete. */ + { + svn_repos_t *repos; + apr_pool_t *subpool = svn_pool_create(pool); + SVN_ERR(svn_test__create_repos(&repos, "test-repo-delete-repos", opts, + subpool)); + path = svn_repos_path(repos, pool); + svn_pool_destroy(subpool); + } + + SVN_ERR(svn_io_check_path(path, &kind, pool)); + SVN_TEST_ASSERT(kind != svn_node_none); + SVN_ERR(svn_repos_delete(path, pool)); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + SVN_TEST_ASSERT(kind == svn_node_none); + + /* Recreate dir so that test cleanup doesn't fail. */ + SVN_ERR(svn_io_dir_make(path, APR_OS_DEFAULT, pool)); + + return SVN_NO_ERROR; +} + +/* Related to issue 4340, "fs layer should reject filenames with trailing \n" */ +static svn_error_t * +filename_with_control_chars(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_pool_t *subpool = svn_pool_create(pool); + svn_repos_t *repos; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t youngest_rev = 0; + svn_error_t *err; + static const char *bad_paths[] = { + "/bar\t", + "/bar\n", + "/\barb\az", + "/\x02 baz", + NULL, + }; + const char *p; + int i; + void *edit_baton; + void *root_baton; + void *out_baton; + const svn_delta_editor_t *editor; + + /* Create the repository. */ + SVN_ERR(svn_test__create_repos(&repos, "test-repo-filename-with-cntrl-chars", + opts, pool)); + fs = svn_repos_fs(repos); + + /* Revision 1: Add a directory /foo */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); + SVN_ERR(svn_fs_make_dir(txn_root, "/foo", subpool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, subpool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + svn_pool_clear(subpool); + + /* Checks for control characters are implemented in the commit editor, + * not in the FS API. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_repos_get_commit_editor4(&editor, &edit_baton, repos, + txn, "file://test", "/", + "plato", "test commit", + dummy_commit_cb, NULL, NULL, NULL, + pool)); + + SVN_ERR(editor->open_root(edit_baton, 1, pool, &root_baton)); + + /* Attempt to copy /foo to a bad path P. This should fail. */ + i = 0; + do + { + p = bad_paths[i++]; + if (p == NULL) + break; + svn_pool_clear(subpool); + err = editor->add_directory(p, root_baton, "/foo", 1, subpool, + &out_baton); + SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_PATH_SYNTAX); + } while (p); + + /* Attempt to add a file with bad path P. This should fail. */ + i = 0; + do + { + p = bad_paths[i++]; + if (p == NULL) + break; + svn_pool_clear(subpool); + err = editor->add_file(p, root_baton, NULL, SVN_INVALID_REVNUM, + subpool, &out_baton); + SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_PATH_SYNTAX); + } while (p); + + + /* Attempt to add a directory with bad path P. This should fail. */ + i = 0; + do + { + p = bad_paths[i++]; + if (p == NULL) + break; + svn_pool_clear(subpool); + err = editor->add_directory(p, root_baton, NULL, SVN_INVALID_REVNUM, + subpool, &out_baton); + SVN_TEST_ASSERT_ERROR(err, SVN_ERR_FS_PATH_SYNTAX); + } while (p); + + SVN_ERR(editor->abort_edit(edit_baton, subpool)); + + return SVN_NO_ERROR; +} + +/* Notification receiver for test_dump_bad_mergeinfo(). This does not + need to do anything, it just needs to exist. + */ +static void +dump_r0_mergeinfo_notifier(void *baton, + const svn_repos_notify_t *notify, + apr_pool_t *scratch_pool) +{ +} + +/* Regression test for part the 'dump' part of issue #4476 "Mergeinfo + containing r0 makes svnsync and svnadmin dump fail". */ +static svn_error_t * +test_dump_r0_mergeinfo(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t youngest_rev = 0; + const svn_string_t *bad_mergeinfo = svn_string_create("/foo:0", pool); + + SVN_ERR(svn_test__create_repos(&repos, "test-repo-dump-r0-mergeinfo", + opts, pool)); + fs = svn_repos_fs(repos); + + /* Revision 1: Any commit will do, here */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_dir(txn_root, "/bar", pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + + /* Revision 2: Add bad mergeinfo */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_change_node_prop(txn_root, "/bar", "svn:mergeinfo", bad_mergeinfo, pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + + /* Test that a dump completes without error. In order to exercise the + functionality under test -- that is, in order for the dump to try to + parse the mergeinfo it is dumping -- the dump must start from a + revision greater than 1 and must take a notification callback. */ + { + svn_stringbuf_t *stringbuf = svn_stringbuf_create_empty(pool); + svn_stream_t *stream = svn_stream_from_stringbuf(stringbuf, pool); + + SVN_ERR(svn_repos_dump_fs3(repos, stream, 2, SVN_INVALID_REVNUM, + FALSE, FALSE, + dump_r0_mergeinfo_notifier, NULL, + NULL, NULL, + pool)); + } + + return SVN_NO_ERROR; +} + + +/* Test dumping in the presence of the property PROP_NAME:PROP_VAL. + * Return the dumped data in *DUMP_DATA_P (if DUMP_DATA_P is not null). + * REPOS is an empty repository. + * See svn_repos_dump_fs3() for START_REV, END_REV, NOTIFY_FUNC, NOTIFY_BATON. + */ +static svn_error_t * +test_dump_bad_props(svn_stringbuf_t **dump_data_p, + svn_repos_t *repos, + const char *prop_name, + const svn_string_t *prop_val, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_repos_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + const char *test_path = "/bar"; + svn_fs_t *fs = svn_repos_fs(repos); + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t youngest_rev = 0; + svn_stringbuf_t *dump_data = svn_stringbuf_create_empty(pool); + svn_stream_t *stream = svn_stream_from_stringbuf(dump_data, pool); + const char *expected_str; + + /* Revision 1: Any commit will do, here */ + SVN_ERR(svn_fs_begin_txn2(&txn, fs, youngest_rev, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_dir(txn_root, test_path , pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + + /* Revision 2: Add the bad property */ + SVN_ERR(svn_fs_begin_txn2(&txn, fs, youngest_rev, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_change_node_prop(txn_root, test_path , prop_name, prop_val, + pool)); + SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &youngest_rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + + /* Test that a dump completes without error. */ + SVN_ERR(svn_repos_dump_fs3(repos, stream, start_rev, end_rev, + FALSE, FALSE, + notify_func, notify_baton, + NULL, NULL, + pool)); + svn_stream_close(stream); + + /* Check that the property appears in the dump data */ + expected_str = apr_psprintf(pool, "K %d\n%s\n" + "V %d\n%s\n" + "PROPS-END\n", + (int)strlen(prop_name), prop_name, + (int)prop_val->len, prop_val->data); + SVN_TEST_ASSERT(strstr(dump_data->data, expected_str)); + + if (dump_data_p) + *dump_data_p = dump_data; + return SVN_NO_ERROR; +} + +/* Test loading in the presence of the property PROP_NAME:PROP_VAL. + * Load data from DUMP_DATA. + * REPOS is an empty repository. + */ +static svn_error_t * +test_load_bad_props(svn_stringbuf_t *dump_data, + svn_repos_t *repos, + const char *prop_name, + const svn_string_t *prop_val, + const char *parent_fspath, + svn_boolean_t validate_props, + svn_repos_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + const char *test_path = apr_psprintf(pool, "%s%s", + parent_fspath ? parent_fspath : "", + "/bar"); + svn_stream_t *stream = svn_stream_from_stringbuf(dump_data, pool); + svn_fs_t *fs; + svn_fs_root_t *rev_root; + svn_revnum_t youngest_rev; + svn_string_t *loaded_prop_val; + + SVN_ERR(svn_repos_load_fs4(repos, stream, + SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, + svn_repos_load_uuid_default, + parent_fspath, + FALSE, FALSE, /*use_*_commit_hook*/ + validate_props, + notify_func, notify_baton, + NULL, NULL, /*cancellation*/ + pool)); + svn_stream_close(stream); + + /* Check the loaded property */ + fs = svn_repos_fs(repos); + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); + SVN_ERR(svn_fs_revision_root(&rev_root, fs, youngest_rev, pool)); + SVN_ERR(svn_fs_node_prop(&loaded_prop_val, + rev_root, test_path, prop_name, pool)); + SVN_TEST_ASSERT(svn_string_compare(loaded_prop_val, prop_val)); + return SVN_NO_ERROR; +} + +static void +load_r0_mergeinfo_notifier(void *baton, + const svn_repos_notify_t *notify, + apr_pool_t *scratch_pool) +{ + svn_boolean_t *had_mergeinfo_warning = baton; + + if (notify->action == svn_repos_notify_warning) + { + if (notify->warning == svn_repos__notify_warning_invalid_mergeinfo) + { + *had_mergeinfo_warning = TRUE; + } + } +} + +/* Regression test for the 'load' part of issue #4476 "Mergeinfo + * containing r0 makes svnsync and svnadmin dump fail". + * + * Bad mergeinfo should not prevent loading a backup, at least when we do not + * require mergeinfo revision numbers or paths to be adjusted during loading. + */ +static svn_error_t * +test_load_r0_mergeinfo(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + const char *prop_name = "svn:mergeinfo"; + const svn_string_t *prop_val = svn_string_create("/foo:0", pool); + svn_stringbuf_t *dump_data = svn_stringbuf_create_empty(pool); + + /* Produce a dump file containing bad mergeinfo */ + { + svn_repos_t *repos; + + SVN_ERR(svn_test__create_repos(&repos, "test-repo-load-r0-mi-1", + opts, pool)); + SVN_ERR(test_dump_bad_props(&dump_data, repos, + prop_name, prop_val, + SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, + NULL, NULL, pool)); + } + + /* Test loading without validating properties: should warn and succeed */ + { + svn_repos_t *repos; + svn_boolean_t had_mergeinfo_warning = FALSE; + + SVN_ERR(svn_test__create_repos(&repos, "test-repo-load-r0-mi-2", + opts, pool)); + + /* Without changing revision numbers or paths */ + SVN_ERR(test_load_bad_props(dump_data, repos, + prop_name, prop_val, + NULL /*parent_dir*/, FALSE /*validate_props*/, + load_r0_mergeinfo_notifier, &had_mergeinfo_warning, + pool)); + SVN_TEST_ASSERT(had_mergeinfo_warning); + + /* With changing revision numbers and/or paths (by loading the same data + again, on top of existing revisions, into subdirectory 'bar') */ + had_mergeinfo_warning = FALSE; + SVN_ERR(test_load_bad_props(dump_data, repos, + prop_name, prop_val, + "/bar", FALSE /*validate_props*/, + load_r0_mergeinfo_notifier, &had_mergeinfo_warning, + pool)); + SVN_TEST_ASSERT(had_mergeinfo_warning); + } + + /* Test loading with validating properties: should return an error */ + { + svn_repos_t *repos; + + SVN_ERR(svn_test__create_repos(&repos, "test-repo-load-r0-mi-3", + opts, pool)); + + /* Without changing revision numbers or paths */ + SVN_TEST__ASSERT_ANY_ERROR(test_load_bad_props(dump_data, repos, + prop_name, prop_val, + NULL /*parent_dir*/, TRUE /*validate_props*/, + NULL, NULL, + pool)); + + /* With changing revision numbers and/or paths (by loading the same data + again, on top of existing revisions, into subdirectory 'bar') */ + SVN_TEST__ASSERT_ANY_ERROR(test_load_bad_props(dump_data, repos, + prop_name, prop_val, + "/bar", TRUE /*validate_props*/, + NULL, NULL, + pool)); + } + + return SVN_NO_ERROR; +} + /* The test table. */ struct svn_test_descriptor_t test_funcs[] = @@ -2546,6 +3543,12 @@ struct svn_test_descriptor_t test_funcs[] = "test removal of defunct locks"), SVN_TEST_PASS2(authz, "test authz access control"), + SVN_TEST_OPTS_PASS(in_repo_authz, + "test authz stored in the repo"), + SVN_TEST_OPTS_PASS(in_repo_groups_authz, + "test authz and global groups stored in the repo"), + SVN_TEST_OPTS_PASS(groups_authz, + "test authz with global groups"), SVN_TEST_OPTS_PASS(commit_editor_authz, "test authz in the commit editor"), SVN_TEST_OPTS_PASS(commit_continue_txn, @@ -2562,5 +3565,13 @@ struct svn_test_descriptor_t test_funcs[] = "test svn_repos_get_file_revsN"), SVN_TEST_OPTS_PASS(issue_4060, "test issue 4060"), + SVN_TEST_OPTS_PASS(test_delete_repos, + "test svn_repos_delete"), + SVN_TEST_OPTS_PASS(filename_with_control_chars, + "test filenames with control characters"), + SVN_TEST_OPTS_PASS(test_dump_r0_mergeinfo, + "test dumping with r0 mergeinfo"), + SVN_TEST_OPTS_PASS(test_load_r0_mergeinfo, + "test loading with r0 mergeinfo"), SVN_TEST_NULL }; |