Fix compiler warning due to missing function prototype.
[svn.git] / subversion / tests / libsvn_fs / locks-test.c
blob59652245e9448bf8957e37af16d72edb16428a8f
1 /* lock-test.c --- tests for the filesystem locking functions
3 * ====================================================================
4 * Copyright (c) 2000-2004 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 * ====================================================================
18 #include <string.h>
19 #include <apr_pools.h>
20 #include <apr_time.h>
22 #include "svn_error.h"
23 #include "svn_fs.h"
25 #include "../svn_test_fs.h"
28 /*-----------------------------------------------------------------*/
30 /** Helper functions **/
32 /* Implementations of the svn_fs_get_locks_callback_t interface and
33 baton, for verifying expected output from svn_fs_get_locks(). */
35 struct get_locks_baton_t
37 apr_hash_t *locks;
40 static svn_error_t *
41 get_locks_callback(void *baton,
42 svn_lock_t *lock,
43 apr_pool_t *pool)
45 struct get_locks_baton_t *b = baton;
46 apr_pool_t *hash_pool = apr_hash_pool_get(b->locks);
47 svn_string_t *lock_path = svn_string_create(lock->path, hash_pool);
48 apr_hash_set(b->locks, lock_path->data, lock_path->len,
49 svn_lock_dup(lock, hash_pool));
50 return SVN_NO_ERROR;
53 /* A factory function. */
55 static struct get_locks_baton_t *
56 make_get_locks_baton(apr_pool_t *pool)
58 struct get_locks_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
59 baton->locks = apr_hash_make(pool);
60 return baton;
64 /* And verification function(s). */
66 static svn_error_t *
67 verify_matching_lock_paths(struct get_locks_baton_t *baton,
68 const char *expected_paths[],
69 apr_size_t num_expected_paths,
70 apr_pool_t *pool)
72 apr_size_t i;
73 if (num_expected_paths != apr_hash_count(baton->locks))
74 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
75 "Unexpected number of locks.");
76 for (i = 0; i < num_expected_paths; i++)
78 const char *path = expected_paths[i];
79 if (! apr_hash_get(baton->locks, path, APR_HASH_KEY_STRING))
80 return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
81 "Missing lock for path '%s'", path);
83 return SVN_NO_ERROR;
87 /*-----------------------------------------------------------------*/
89 /** The actual lock-tests called by `make check` **/
93 /* Test that we can create a lock--nothing more. */
94 static svn_error_t *
95 lock_only(const char **msg,
96 svn_boolean_t msg_only,
97 svn_test_opts_t *opts,
98 apr_pool_t *pool)
100 svn_fs_t *fs;
101 svn_fs_txn_t *txn;
102 svn_fs_root_t *txn_root;
103 const char *conflict;
104 svn_revnum_t newrev;
105 svn_fs_access_t *access;
106 svn_lock_t *mylock;
108 *msg = "lock only";
110 if (msg_only)
111 return SVN_NO_ERROR;
113 /* Prepare a filesystem and a new txn. */
114 SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-only",
115 opts->fs_type, pool));
116 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
117 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
119 /* Create the greek tree and commit it. */
120 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
121 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
123 /* We are now 'bubba'. */
124 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
125 SVN_ERR(svn_fs_set_access(fs, access));
127 /* Lock /A/D/G/rho. */
128 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
129 SVN_INVALID_REVNUM, FALSE, pool));
131 return SVN_NO_ERROR;
138 /* Test that we can create, fetch, and destroy a lock. It exercises
139 each of the five public fs locking functions. */
140 static svn_error_t *
141 lookup_lock_by_path(const char **msg,
142 svn_boolean_t msg_only,
143 svn_test_opts_t *opts,
144 apr_pool_t *pool)
146 svn_fs_t *fs;
147 svn_fs_txn_t *txn;
148 svn_fs_root_t *txn_root;
149 const char *conflict;
150 svn_revnum_t newrev;
151 svn_fs_access_t *access;
152 svn_lock_t *mylock, *somelock;
154 *msg = "lookup lock by path";
156 if (msg_only)
157 return SVN_NO_ERROR;
159 /* Prepare a filesystem and a new txn. */
160 SVN_ERR(svn_test__create_fs(&fs, "test-repo-lookup-lock-by-path",
161 opts->fs_type, pool));
162 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
163 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
165 /* Create the greek tree and commit it. */
166 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
167 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
169 /* We are now 'bubba'. */
170 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
171 SVN_ERR(svn_fs_set_access(fs, access));
173 /* Lock /A/D/G/rho. */
174 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
175 SVN_INVALID_REVNUM, FALSE, pool));
177 /* Can we look up the lock by path? */
178 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
179 if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0))
180 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
181 "Couldn't look up a lock by pathname.");
183 return SVN_NO_ERROR;
186 /* Test that we can create a lock outside of the fs and attach it to a
187 path. */
188 static svn_error_t *
189 attach_lock(const char **msg,
190 svn_boolean_t msg_only,
191 svn_test_opts_t *opts,
192 apr_pool_t *pool)
194 svn_fs_t *fs;
195 svn_fs_txn_t *txn;
196 svn_fs_root_t *txn_root;
197 const char *conflict;
198 svn_revnum_t newrev;
199 svn_fs_access_t *access;
200 svn_lock_t *somelock;
201 svn_lock_t *mylock;
202 const char *token;
204 *msg = "attach lock";
206 if (msg_only)
207 return SVN_NO_ERROR;
209 /* Prepare a filesystem and a new txn. */
210 SVN_ERR(svn_test__create_fs(&fs, "test-repo-attach-lock",
211 opts->fs_type, pool));
212 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
213 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
215 /* Create the greek tree and commit it. */
216 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
217 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
219 /* We are now 'bubba'. */
220 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
221 SVN_ERR(svn_fs_set_access(fs, access));
223 SVN_ERR(svn_fs_generate_lock_token(&token, fs, pool));
224 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", token,
225 "This is a comment. Yay comment!", 0,
226 apr_time_now() + apr_time_from_sec(3),
227 SVN_INVALID_REVNUM, FALSE, pool));
229 /* Can we look up the lock by path? */
230 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
231 if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0))
232 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
233 "Couldn't look up a lock by pathname.");
235 /* Unlock /A/D/G/rho, and verify that it's gone. */
236 SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token, 0, pool));
237 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
238 if (somelock)
239 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
240 "Removed a lock, but it's still there.");
242 return SVN_NO_ERROR;
246 /* Test that we can get all locks under a directory. */
247 static svn_error_t *
248 get_locks(const char **msg,
249 svn_boolean_t msg_only,
250 svn_test_opts_t *opts,
251 apr_pool_t *pool)
253 svn_fs_t *fs;
254 svn_fs_txn_t *txn;
255 svn_fs_root_t *txn_root;
256 const char *conflict;
257 svn_revnum_t newrev;
258 svn_fs_access_t *access;
259 svn_lock_t *mylock;
260 struct get_locks_baton_t *get_locks_baton;
261 apr_size_t i, num_expected_paths;
263 *msg = "get locks";
265 if (msg_only)
266 return SVN_NO_ERROR;
268 /* Prepare a filesystem and a new txn. */
269 SVN_ERR(svn_test__create_fs(&fs, "test-repo-get-locks",
270 opts->fs_type, pool));
271 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
272 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
274 /* Create the greek tree and commit it. */
275 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
276 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
278 /* We are now 'bubba'. */
279 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
280 SVN_ERR(svn_fs_set_access(fs, access));
282 /* Lock our paths; verify from "/". */
284 static const char *expected_paths[] = {
285 "/A/D/G/pi",
286 "/A/D/G/rho",
287 "/A/D/G/tau",
288 "/A/D/H/psi",
289 "/A/D/H/chi",
290 "/A/D/H/omega",
291 "/A/B/E/alpha",
292 "/A/B/E/beta",
294 num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
295 for (i = 0; i < num_expected_paths; i++)
297 SVN_ERR(svn_fs_lock(&mylock, fs, expected_paths[i], NULL, "", 0, 0,
298 SVN_INVALID_REVNUM, FALSE, pool));
300 get_locks_baton = make_get_locks_baton(pool);
301 SVN_ERR(svn_fs_get_locks(fs, "", get_locks_callback,
302 get_locks_baton, pool));
303 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
304 num_expected_paths, pool));
307 /* Verify from "/A/B". */
309 static const char *expected_paths[] = {
310 "/A/B/E/alpha",
311 "/A/B/E/beta",
313 num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
314 get_locks_baton = make_get_locks_baton(pool);
315 SVN_ERR(svn_fs_get_locks(fs, "A/B", get_locks_callback,
316 get_locks_baton, pool));
317 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
318 num_expected_paths, pool));
321 /* Verify from "/A/D". */
323 static const char *expected_paths[] = {
324 "/A/D/G/pi",
325 "/A/D/G/rho",
326 "/A/D/G/tau",
327 "/A/D/H/psi",
328 "/A/D/H/chi",
329 "/A/D/H/omega",
331 num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
332 get_locks_baton = make_get_locks_baton(pool);
333 SVN_ERR(svn_fs_get_locks(fs, "A/D", get_locks_callback,
334 get_locks_baton, pool));
335 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
336 num_expected_paths, pool));
339 /* Verify from "/A/D/G". */
341 static const char *expected_paths[] = {
342 "/A/D/G/pi",
343 "/A/D/G/rho",
344 "/A/D/G/tau",
346 num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
347 get_locks_baton = make_get_locks_baton(pool);
348 SVN_ERR(svn_fs_get_locks(fs, "A/D/G", get_locks_callback,
349 get_locks_baton, pool));
350 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
351 num_expected_paths, pool));
354 /* Verify from "/A/D/H/omega". */
356 static const char *expected_paths[] = {
357 "/A/D/H/omega",
359 num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
360 get_locks_baton = make_get_locks_baton(pool);
361 SVN_ERR(svn_fs_get_locks(fs, "A/D/H/omega", get_locks_callback,
362 get_locks_baton, pool));
363 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
364 num_expected_paths, pool));
367 /* Verify from "/iota" (which wasn't locked... tricky...). */
369 static const char *expected_paths[] = { 0 };
370 num_expected_paths = 0;
371 get_locks_baton = make_get_locks_baton(pool);
372 SVN_ERR(svn_fs_get_locks(fs, "iota", get_locks_callback,
373 get_locks_baton, pool));
374 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
375 num_expected_paths, pool));
378 return SVN_NO_ERROR;
382 /* Test that we can create, fetch, and destroy a lock. It exercises
383 each of the five public fs locking functions. */
384 static svn_error_t *
385 basic_lock(const char **msg,
386 svn_boolean_t msg_only,
387 svn_test_opts_t *opts,
388 apr_pool_t *pool)
390 svn_fs_t *fs;
391 svn_fs_txn_t *txn;
392 svn_fs_root_t *txn_root;
393 const char *conflict;
394 svn_revnum_t newrev;
395 svn_fs_access_t *access;
396 svn_lock_t *mylock, *somelock;
398 *msg = "basic locking";
400 if (msg_only)
401 return SVN_NO_ERROR;
403 /* Prepare a filesystem and a new txn. */
404 SVN_ERR(svn_test__create_fs(&fs, "test-repo-basic-lock",
405 opts->fs_type, pool));
406 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
407 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
409 /* Create the greek tree and commit it. */
410 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
411 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
413 /* We are now 'bubba'. */
414 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
415 SVN_ERR(svn_fs_set_access(fs, access));
417 /* Lock /A/D/G/rho. */
418 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
419 SVN_INVALID_REVNUM, FALSE, pool));
421 /* Can we look up the lock by path? */
422 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
423 if ((! somelock) || (strcmp(somelock->token, mylock->token) != 0))
424 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
425 "Couldn't look up a lock by pathname.");
427 /* Unlock /A/D/G/rho, and verify that it's gone. */
428 SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token, 0, pool));
429 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
430 if (somelock)
431 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
432 "Removed a lock, but it's still there.");
434 return SVN_NO_ERROR;
439 /* Test that locks are enforced -- specifically that both a username
440 and token are required to make use of the lock. */
441 static svn_error_t *
442 lock_credentials(const char **msg,
443 svn_boolean_t msg_only,
444 svn_test_opts_t *opts,
445 apr_pool_t *pool)
447 svn_fs_t *fs;
448 svn_fs_txn_t *txn;
449 svn_fs_root_t *txn_root;
450 const char *conflict;
451 svn_revnum_t newrev;
452 svn_fs_access_t *access;
453 svn_lock_t *mylock;
454 svn_error_t *err;
456 *msg = "test that locking requires proper credentials";
458 if (msg_only)
459 return SVN_NO_ERROR;
461 /* Prepare a filesystem and a new txn. */
462 SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-credentials",
463 opts->fs_type, pool));
464 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
465 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
467 /* Create the greek tree and commit it. */
468 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
469 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
471 /* We are now 'bubba'. */
472 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
473 SVN_ERR(svn_fs_set_access(fs, access));
475 /* Lock /A/D/G/rho. */
476 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
477 SVN_INVALID_REVNUM, FALSE, pool));
479 /* Push the proper lock-token into the fs access context. */
480 SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token));
482 /* Make a new transaction and change rho. */
483 SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
484 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
485 SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho",
486 "new contents", pool));
488 /* We are no longer 'bubba'. We're nobody. */
489 SVN_ERR(svn_fs_set_access(fs, NULL));
491 /* Try to commit the file change. Should fail, because we're nobody. */
492 err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
493 if (! err)
494 return svn_error_create
495 (SVN_ERR_TEST_FAILED, NULL,
496 "Uhoh, able to commit locked file without any fs username.");
497 svn_error_clear(err);
499 /* We are now 'hortense'. */
500 SVN_ERR(svn_fs_create_access(&access, "hortense", pool));
501 SVN_ERR(svn_fs_set_access(fs, access));
503 /* Try to commit the file change. Should fail, because we're 'hortense'. */
504 err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
505 if (! err)
506 return svn_error_create
507 (SVN_ERR_TEST_FAILED, NULL,
508 "Uhoh, able to commit locked file as non-owner.");
509 svn_error_clear(err);
511 /* Be 'bubba' again. */
512 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
513 SVN_ERR(svn_fs_set_access(fs, access));
515 /* Try to commit the file change. Should fail, because there's no token. */
516 err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
517 if (! err)
518 return svn_error_create
519 (SVN_ERR_TEST_FAILED, NULL,
520 "Uhoh, able to commit locked file with no lock token.");
521 svn_error_clear(err);
523 /* Push the proper lock-token into the fs access context. */
524 SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token));
526 /* Commit should now succeed. */
527 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
529 return SVN_NO_ERROR;
534 /* Test that locks are enforced at commit time. Somebody might lock
535 something behind your back, right before you run
536 svn_fs_commit_txn(). Also, this test verifies that recursive
537 lock-checks on directories is working properly. */
538 static svn_error_t *
539 final_lock_check(const char **msg,
540 svn_boolean_t msg_only,
541 svn_test_opts_t *opts,
542 apr_pool_t *pool)
544 svn_fs_t *fs;
545 svn_fs_txn_t *txn;
546 svn_fs_root_t *txn_root;
547 const char *conflict;
548 svn_revnum_t newrev;
549 svn_fs_access_t *access;
550 svn_lock_t *mylock;
551 svn_error_t *err;
553 *msg = "test that locking is enforced in final commit step";
555 if (msg_only)
556 return SVN_NO_ERROR;
558 /* Prepare a filesystem and a new txn. */
559 SVN_ERR(svn_test__create_fs(&fs, "test-repo-final-lock-check",
560 opts->fs_type, pool));
561 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
562 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
564 /* Create the greek tree and commit it. */
565 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
566 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
568 /* Make a new transaction and delete "/A" */
569 SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
570 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
571 SVN_ERR(svn_fs_delete(txn_root, "/A", pool));
573 /* Become 'bubba' and lock "/A/D/G/rho". */
574 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
575 SVN_ERR(svn_fs_set_access(fs, access));
576 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
577 SVN_INVALID_REVNUM, FALSE, pool));
579 /* We are no longer 'bubba'. We're nobody. */
580 SVN_ERR(svn_fs_set_access(fs, NULL));
582 /* Try to commit the transaction. Should fail, because a child of
583 the deleted directory is locked by someone else. */
584 err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
585 if (! err)
586 return svn_error_create
587 (SVN_ERR_TEST_FAILED, NULL,
588 "Uhoh, able to commit dir deletion when a child is locked.");
589 svn_error_clear(err);
591 /* Supply correct username and token; commit should work. */
592 SVN_ERR(svn_fs_set_access(fs, access));
593 SVN_ERR(svn_fs_access_add_lock_token(access, mylock->token));
594 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
596 return SVN_NO_ERROR;
601 /* If a directory's child is locked by someone else, we should still
602 be able to commit a propchange on the directory. */
603 static svn_error_t *
604 lock_dir_propchange(const char **msg,
605 svn_boolean_t msg_only,
606 svn_test_opts_t *opts,
607 apr_pool_t *pool)
609 svn_fs_t *fs;
610 svn_fs_txn_t *txn;
611 svn_fs_root_t *txn_root;
612 const char *conflict;
613 svn_revnum_t newrev;
614 svn_fs_access_t *access;
615 svn_lock_t *mylock;
617 *msg = "dir propchange can be committed with locked child";
619 if (msg_only)
620 return SVN_NO_ERROR;
622 /* Prepare a filesystem and a new txn. */
623 SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-dir-propchange",
624 opts->fs_type, pool));
625 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
626 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
628 /* Create the greek tree and commit it. */
629 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
630 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
632 /* Become 'bubba' and lock "/A/D/G/rho". */
633 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
634 SVN_ERR(svn_fs_set_access(fs, access));
635 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
636 SVN_INVALID_REVNUM, FALSE, pool));
638 /* We are no longer 'bubba'. We're nobody. */
639 SVN_ERR(svn_fs_set_access(fs, NULL));
641 /* Make a new transaction and make a propchange on "/A" */
642 SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
643 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
644 SVN_ERR(svn_fs_change_node_prop(txn_root, "/A",
645 "foo", svn_string_create("bar", pool),
646 pool));
648 /* Commit should succeed; this means we're doing a non-recursive
649 lock-check on directory, rather than a recursive one. */
650 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
652 return SVN_NO_ERROR;
657 /* DAV clients sometimes LOCK non-existent paths, as a way of
658 reserving names. Check that this technique works. */
659 static svn_error_t *
660 lock_name_reservation(const char **msg,
661 svn_boolean_t msg_only,
662 svn_test_opts_t *opts,
663 apr_pool_t *pool)
665 svn_fs_t *fs;
666 svn_fs_txn_t *txn;
667 svn_fs_root_t *txn_root, *rev_root;
668 const char *conflict;
669 svn_revnum_t newrev;
670 svn_fs_access_t *access;
671 svn_lock_t *mylock;
672 svn_error_t *err;
674 *msg = "able to reserve a name (lock non-existent path)";
676 if (msg_only)
677 return SVN_NO_ERROR;
679 /* Prepare a filesystem and a new txn. */
680 SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-name-reservation",
681 opts->fs_type, pool));
682 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
683 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
685 /* Create the greek tree and commit it. */
686 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
687 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
689 /* Become 'bubba' and lock imaginary path "/A/D/G2/blooga". */
690 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
691 SVN_ERR(svn_fs_set_access(fs, access));
692 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G2/blooga", NULL, "", 0, 0,
693 SVN_INVALID_REVNUM, FALSE, pool));
695 /* We are no longer 'bubba'. We're nobody. */
696 SVN_ERR(svn_fs_set_access(fs, NULL));
698 /* Make a new transaction. */
699 SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
700 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
702 /* This copy should fail, because an imaginary path in the target of
703 the copy is reserved by someone else. */
704 SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, pool));
705 err = svn_fs_copy(rev_root, "/A/D/G", txn_root, "/A/D/G2", pool);
706 if (! err)
707 return svn_error_create
708 (SVN_ERR_TEST_FAILED, NULL,
709 "Uhoh, copy succeeded when path within target was locked.");
710 svn_error_clear(err);
712 return SVN_NO_ERROR;
716 /* Test that we can set and get locks in and under a directory. We'll
717 use non-existent FS paths for this test, though, as the FS API
718 currently disallows directory locking. */
719 static svn_error_t *
720 directory_locks_kinda(const char **msg,
721 svn_boolean_t msg_only,
722 svn_test_opts_t *opts,
723 apr_pool_t *pool)
725 svn_fs_t *fs;
726 svn_fs_txn_t *txn;
727 svn_fs_root_t *txn_root;
728 svn_fs_access_t *access;
729 svn_lock_t *mylock;
730 apr_size_t num_expected_paths, i;
731 struct get_locks_baton_t *get_locks_baton;
733 *msg = "directory locks (kinda)";
735 if (msg_only)
736 return SVN_NO_ERROR;
738 /* Prepare a filesystem and a new txn. */
739 SVN_ERR(svn_test__create_fs(&fs, "test-repo-directory-locks-kinda",
740 opts->fs_type, pool));
741 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
742 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
744 /* We are now 'bubba'. */
745 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
746 SVN_ERR(svn_fs_set_access(fs, access));
748 /*** Lock some various, non-existent, yet dir-name-spacily
749 overlapping paths; verify. ***/
751 static const char *expected_paths[] = {
752 "/Program Files/Tigris.org/Subversion",
753 "/Program Files/Tigris.org",
754 "/Stuff/Junk/Fluff",
755 "/Program Files",
757 num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
759 /* Lock all paths under /A/D/G. */
760 for (i = 0; i < num_expected_paths; i++)
762 SVN_ERR(svn_fs_lock(&mylock, fs, expected_paths[i], NULL, "", 0, 0,
763 SVN_INVALID_REVNUM, FALSE, pool));
765 get_locks_baton = make_get_locks_baton(pool);
766 SVN_ERR(svn_fs_get_locks(fs, "/", get_locks_callback,
767 get_locks_baton, pool));
768 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
769 num_expected_paths, pool));
772 /*** Now unlock a "middle directory" ***/
774 static const char *expected_paths[] = {
775 "/Program Files/Tigris.org/Subversion",
776 "/Stuff/Junk/Fluff",
777 "/Program Files",
779 num_expected_paths = sizeof(expected_paths) / sizeof(const char *);
781 SVN_ERR(svn_fs_unlock(fs, "/Program Files/Tigris.org", NULL,
782 TRUE, pool));
783 get_locks_baton = make_get_locks_baton(pool);
784 SVN_ERR(svn_fs_get_locks(fs, "/", get_locks_callback,
785 get_locks_baton, pool));
786 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
787 num_expected_paths, pool));
790 return SVN_NO_ERROR;
794 /* Test that locks auto-expire correctly. */
795 static svn_error_t *
796 lock_expiration(const char **msg,
797 svn_boolean_t msg_only,
798 svn_test_opts_t *opts,
799 apr_pool_t *pool)
801 svn_fs_t *fs;
802 svn_fs_txn_t *txn;
803 svn_fs_root_t *txn_root;
804 const char *conflict;
805 svn_revnum_t newrev;
806 svn_fs_access_t *access;
807 svn_lock_t *mylock;
808 svn_error_t *err;
809 struct get_locks_baton_t *get_locks_baton;
811 *msg = "test that locks can expire";
813 if (msg_only)
814 return SVN_NO_ERROR;
816 /* Prepare a filesystem and a new txn. */
817 SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-expiration",
818 opts->fs_type, pool));
819 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
820 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
822 /* Create the greek tree and commit it. */
823 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
824 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
826 /* Make a new transaction and change rho. */
827 SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
828 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
829 SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho",
830 "new contents", pool));
832 /* We are now 'bubba'. */
833 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
834 SVN_ERR(svn_fs_set_access(fs, access));
836 /* Lock /A/D/G/rho, with an expiration 3 seconds from now. */
837 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0,
838 apr_time_now() + apr_time_from_sec(3),
839 SVN_INVALID_REVNUM, FALSE, pool));
841 /* Become nobody. */
842 SVN_ERR(svn_fs_set_access(fs, NULL));
844 /* Try to commit. Should fail because we're 'nobody', and the lock
845 hasn't expired yet. */
846 err = svn_fs_commit_txn(&conflict, &newrev, txn, pool);
847 if (! err)
848 return svn_error_create
849 (SVN_ERR_TEST_FAILED, NULL,
850 "Uhoh, able to commit a file that has a non-expired lock.");
851 svn_error_clear(err);
853 /* Check that the lock is there, by getting it via the paths parent. */
855 static const char *expected_paths [] = {
856 "/A/D/G/rho"
858 apr_size_t num_expected_paths = (sizeof(expected_paths)
859 / sizeof(expected_paths[0]));
860 get_locks_baton = make_get_locks_baton(pool);
861 SVN_ERR(svn_fs_get_locks(fs, "/A/D/G", get_locks_callback,
862 get_locks_baton, pool));
863 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
864 num_expected_paths, pool));
867 /* Sleep 5 seconds, so the lock auto-expires. Anonymous commit
868 should then succeed. */
869 apr_sleep(apr_time_from_sec(5));
871 /* Verify that the lock auto-expired even in the recursive case. */
873 static const char *expected_paths [] = { 0 };
874 apr_size_t num_expected_paths = 0;
875 get_locks_baton = make_get_locks_baton(pool);
876 SVN_ERR(svn_fs_get_locks(fs, "/A/D/G", get_locks_callback,
877 get_locks_baton, pool));
878 SVN_ERR(verify_matching_lock_paths(get_locks_baton, expected_paths,
879 num_expected_paths, pool));
882 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
884 return SVN_NO_ERROR;
887 /* Test that a lock can be broken, stolen, or refreshed */
888 static svn_error_t *
889 lock_break_steal_refresh(const char **msg,
890 svn_boolean_t msg_only,
891 svn_test_opts_t *opts,
892 apr_pool_t *pool)
894 svn_fs_t *fs;
895 svn_fs_txn_t *txn;
896 svn_fs_root_t *txn_root;
897 const char *conflict;
898 svn_revnum_t newrev;
899 svn_fs_access_t *access;
900 svn_lock_t *mylock, *somelock;
902 *msg = "breaking, stealing, refreshing a lock";
904 if (msg_only)
905 return SVN_NO_ERROR;
907 /* Prepare a filesystem and a new txn. */
908 SVN_ERR(svn_test__create_fs(&fs, "test-repo-steal-refresh",
909 opts->fs_type, pool));
910 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
911 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
913 /* Create the greek tree and commit it. */
914 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
915 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
917 /* Become 'bubba' and lock "/A/D/G/rho". */
918 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
919 SVN_ERR(svn_fs_set_access(fs, access));
920 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
921 SVN_INVALID_REVNUM, FALSE, pool));
923 /* Become 'hortense' and break bubba's lock, then verify it's gone. */
924 SVN_ERR(svn_fs_create_access(&access, "hortense", pool));
925 SVN_ERR(svn_fs_set_access(fs, access));
926 SVN_ERR(svn_fs_unlock(fs, mylock->path, mylock->token,
927 1 /* FORCE BREAK */, pool));
928 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
929 if (somelock)
930 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
931 "Tried to break a lock, but it's still there.");
933 /* As hortense, create a new lock, and verify that we own it. */
934 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0,
935 SVN_INVALID_REVNUM, FALSE, pool));
936 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
937 if (strcmp(somelock->owner, mylock->owner) != 0)
938 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
939 "Made a lock, but we don't seem to own it.");
941 /* As bubba, steal hortense's lock, creating a new one that expires. */
942 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
943 SVN_ERR(svn_fs_set_access(fs, access));
944 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0,
945 apr_time_now() + apr_time_from_sec(300), /* 5 min. */
946 SVN_INVALID_REVNUM,
947 TRUE /* FORCE STEAL */,
948 pool));
949 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
950 if (strcmp(somelock->owner, mylock->owner) != 0)
951 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
952 "Made a lock, but we don't seem to own it.");
953 if (! somelock->expiration_date)
954 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
955 "Made expiring lock, but seems not to expire.");
957 /* Refresh the lock, so that it never expires. */
958 SVN_ERR(svn_fs_lock(&somelock, fs, somelock->path, somelock->token,
959 somelock->comment, 0, 0,
960 SVN_INVALID_REVNUM,
961 TRUE /* FORCE STEAL */,
962 pool));
963 SVN_ERR(svn_fs_get_lock(&somelock, fs, "/A/D/G/rho", pool));
964 if (somelock->expiration_date)
965 return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
966 "Made non-expirirng lock, but it expires.");
968 return SVN_NO_ERROR;
972 /* Test that svn_fs_lock() and svn_fs_attach_lock() can do
973 out-of-dateness checks.. */
974 static svn_error_t *
975 lock_out_of_date(const char **msg,
976 svn_boolean_t msg_only,
977 svn_test_opts_t *opts,
978 apr_pool_t *pool)
980 svn_fs_t *fs;
981 svn_fs_txn_t *txn;
982 svn_fs_root_t *txn_root;
983 const char *conflict;
984 svn_revnum_t newrev;
985 svn_fs_access_t *access;
986 svn_lock_t *mylock;
987 svn_error_t *err;
989 *msg = "check out-of-dateness before locking";
991 if (msg_only)
992 return SVN_NO_ERROR;
994 /* Prepare a filesystem and a new txn. */
995 SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-out-of-date",
996 opts->fs_type, pool));
997 SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
998 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1000 /* Create the greek tree and commit it. */
1001 SVN_ERR(svn_test__create_greek_tree(txn_root, pool));
1002 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
1004 /* Commit a small change to /A/D/G/rho, creating revision 2. */
1005 SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
1006 SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
1007 SVN_ERR(svn_test__set_file_contents(txn_root, "/A/D/G/rho",
1008 "new contents", pool));
1009 SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
1011 /* We are now 'bubba'. */
1012 SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
1013 SVN_ERR(svn_fs_set_access(fs, access));
1015 /* Try to lock /A/D/G/rho, but claim that we still have r1 of the file. */
1016 err = svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0, 0, 1, FALSE, pool);
1017 if (! err)
1018 return svn_error_create
1019 (SVN_ERR_TEST_FAILED, NULL,
1020 "Uhoh, able to lock an out-of-date file.");
1021 svn_error_clear(err);
1023 /* Attempt lock again, this time claiming to have r2. */
1024 SVN_ERR(svn_fs_lock(&mylock, fs, "/A/D/G/rho", NULL, "", 0,
1025 0, 2, FALSE, pool));
1027 /* 'Refresh' the lock, claiming to have r1... should fail. */
1028 err = svn_fs_lock(&mylock, fs, mylock->path,
1029 mylock->token, mylock->comment, 0,
1030 apr_time_now() + apr_time_from_sec(50),
1032 TRUE /* FORCE STEAL */,
1033 pool);
1034 if (! err)
1035 return svn_error_create
1036 (SVN_ERR_TEST_FAILED, NULL,
1037 "Uhoh, able to refresh a lock on an out-of-date file.");
1038 svn_error_clear(err);
1040 return SVN_NO_ERROR;
1045 /* ------------------------------------------------------------------------ */
1047 /* The test table. */
1049 struct svn_test_descriptor_t test_funcs[] =
1051 SVN_TEST_NULL,
1052 SVN_TEST_PASS(lock_only),
1053 SVN_TEST_PASS(lookup_lock_by_path),
1054 SVN_TEST_PASS(attach_lock),
1055 SVN_TEST_PASS(get_locks),
1056 SVN_TEST_PASS(basic_lock),
1057 SVN_TEST_PASS(lock_credentials),
1058 SVN_TEST_PASS(final_lock_check),
1059 SVN_TEST_PASS(lock_dir_propchange),
1060 SVN_TEST_XFAIL(lock_name_reservation),
1061 SVN_TEST_XFAIL(directory_locks_kinda),
1062 SVN_TEST_PASS(lock_expiration),
1063 SVN_TEST_PASS(lock_break_steal_refresh),
1064 SVN_TEST_PASS(lock_out_of_date),
1065 SVN_TEST_NULL