|
13 | 13 |
|
14 | 14 | #include <stdint.h>
|
15 | 15 | #include <vector>
|
| 16 | +#ifndef WIN32 |
| 17 | +#include <sys/types.h> |
| 18 | +#include <sys/wait.h> |
| 19 | +#endif |
16 | 20 |
|
17 | 21 | #include <boost/test/unit_test.hpp>
|
18 | 22 |
|
@@ -603,4 +607,130 @@ BOOST_AUTO_TEST_CASE(test_ParseFixedPoint)
|
603 | 607 | BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount));
|
604 | 608 | }
|
605 | 609 |
|
| 610 | +static void TestOtherThread(fs::path dirname, std::string lockname, bool *result) |
| 611 | +{ |
| 612 | + *result = LockDirectory(dirname, lockname); |
| 613 | +} |
| 614 | + |
| 615 | +#ifndef WIN32 // Cannot do this test on WIN32 due to lack of fork() |
| 616 | +static constexpr char LockCommand = 'L'; |
| 617 | +static constexpr char UnlockCommand = 'U'; |
| 618 | +static constexpr char ExitCommand = 'X'; |
| 619 | + |
| 620 | +static void TestOtherProcess(fs::path dirname, std::string lockname, int fd) |
| 621 | +{ |
| 622 | + char ch; |
| 623 | + int rv; |
| 624 | + while (true) { |
| 625 | + rv = read(fd, &ch, 1); // Wait for command |
| 626 | + assert(rv == 1); |
| 627 | + switch(ch) { |
| 628 | + case LockCommand: |
| 629 | + ch = LockDirectory(dirname, lockname); |
| 630 | + rv = write(fd, &ch, 1); |
| 631 | + assert(rv == 1); |
| 632 | + break; |
| 633 | + case UnlockCommand: |
| 634 | + ReleaseDirectoryLocks(); |
| 635 | + ch = true; // Always succeeds |
| 636 | + rv = write(fd, &ch, 1); |
| 637 | + break; |
| 638 | + case ExitCommand: |
| 639 | + close(fd); |
| 640 | + exit(0); |
| 641 | + default: |
| 642 | + assert(0); |
| 643 | + } |
| 644 | + } |
| 645 | +} |
| 646 | +#endif |
| 647 | + |
| 648 | +BOOST_AUTO_TEST_CASE(test_LockDirectory) |
| 649 | +{ |
| 650 | + fs::path dirname = fs::temp_directory_path() / fs::unique_path(); |
| 651 | + const std::string lockname = ".lock"; |
| 652 | +#ifndef WIN32 |
| 653 | + // Revert SIGCHLD to default, otherwise boost.test will catch and fail on |
| 654 | + // it: there is BOOST_TEST_IGNORE_SIGCHLD but that only works when defined |
| 655 | + // at build-time of the boost library |
| 656 | + void (*old_handler)(int) = signal(SIGCHLD, SIG_DFL); |
| 657 | + |
| 658 | + // Fork another process for testing before creating the lock, so that we |
| 659 | + // won't fork while holding the lock (which might be undefined, and is not |
| 660 | + // relevant as test case as that is avoided with -daemonize). |
| 661 | + int fd[2]; |
| 662 | + BOOST_CHECK_EQUAL(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), 0); |
| 663 | + pid_t pid = fork(); |
| 664 | + if (!pid) { |
| 665 | + BOOST_CHECK_EQUAL(close(fd[1]), 0); // Child: close parent end |
| 666 | + TestOtherProcess(dirname, lockname, fd[0]); |
| 667 | + } |
| 668 | + BOOST_CHECK_EQUAL(close(fd[0]), 0); // Parent: close child end |
| 669 | +#endif |
| 670 | + // Lock on non-existent directory should fail |
| 671 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), false); |
| 672 | + |
| 673 | + fs::create_directories(dirname); |
| 674 | + |
| 675 | + // Probing lock on new directory should succeed |
| 676 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); |
| 677 | + |
| 678 | + // Persistent lock on new directory should succeed |
| 679 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true); |
| 680 | + |
| 681 | + // Another lock on the directory from the same thread should succeed |
| 682 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname), true); |
| 683 | + |
| 684 | + // Another lock on the directory from a different thread within the same process should succeed |
| 685 | + bool threadresult; |
| 686 | + std::thread thr(TestOtherThread, dirname, lockname, &threadresult); |
| 687 | + thr.join(); |
| 688 | + BOOST_CHECK_EQUAL(threadresult, true); |
| 689 | +#ifndef WIN32 |
| 690 | + // Try to aquire lock in child process while we're holding it, this should fail. |
| 691 | + char ch; |
| 692 | + BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); |
| 693 | + BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); |
| 694 | + BOOST_CHECK_EQUAL((bool)ch, false); |
| 695 | + |
| 696 | + // Give up our lock |
| 697 | + ReleaseDirectoryLocks(); |
| 698 | + // Probing lock from our side now should succeed, but not hold on to the lock. |
| 699 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); |
| 700 | + |
| 701 | + // Try to acquire the lock in the child process, this should be succesful. |
| 702 | + BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); |
| 703 | + BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); |
| 704 | + BOOST_CHECK_EQUAL((bool)ch, true); |
| 705 | + |
| 706 | + // When we try to probe the lock now, it should fail. |
| 707 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), false); |
| 708 | + |
| 709 | + // Unlock the lock in the child process |
| 710 | + BOOST_CHECK_EQUAL(write(fd[1], &UnlockCommand, 1), 1); |
| 711 | + BOOST_CHECK_EQUAL(read(fd[1], &ch, 1), 1); |
| 712 | + BOOST_CHECK_EQUAL((bool)ch, true); |
| 713 | + |
| 714 | + // When we try to probe the lock now, it should succeed. |
| 715 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); |
| 716 | + |
| 717 | + // Re-lock the lock in the child process, then wait for it to exit, check |
| 718 | + // successful return. After that, we check that exiting the process |
| 719 | + // has released the lock as we would expect by probing it. |
| 720 | + int processstatus; |
| 721 | + BOOST_CHECK_EQUAL(write(fd[1], &LockCommand, 1), 1); |
| 722 | + BOOST_CHECK_EQUAL(write(fd[1], &ExitCommand, 1), 1); |
| 723 | + BOOST_CHECK_EQUAL(waitpid(pid, &processstatus, 0), pid); |
| 724 | + BOOST_CHECK_EQUAL(processstatus, 0); |
| 725 | + BOOST_CHECK_EQUAL(LockDirectory(dirname, lockname, true), true); |
| 726 | + |
| 727 | + // Restore SIGCHLD |
| 728 | + signal(SIGCHLD, old_handler); |
| 729 | + BOOST_CHECK_EQUAL(close(fd[1]), 0); // Close our side of the socketpair |
| 730 | +#endif |
| 731 | + // Clean up |
| 732 | + ReleaseDirectoryLocks(); |
| 733 | + fs::remove_all(dirname); |
| 734 | +} |
| 735 | + |
606 | 736 | BOOST_AUTO_TEST_SUITE_END()
|
0 commit comments