diff options
Diffstat (limited to 'test/bbackupd/testbbackupd.cpp')
-rw-r--r-- | test/bbackupd/testbbackupd.cpp | 3526 |
1 files changed, 1816 insertions, 1710 deletions
diff --git a/test/bbackupd/testbbackupd.cpp b/test/bbackupd/testbbackupd.cpp index 6cd8f27d..cc602f22 100644 --- a/test/bbackupd/testbbackupd.cpp +++ b/test/bbackupd/testbbackupd.cpp @@ -21,7 +21,6 @@ #include <sys/stat.h> #include <limits.h> #include <string.h> -#include <unistd.h> #ifdef HAVE_SYS_WAIT_H #include <sys/wait.h> @@ -36,18 +35,24 @@ #include <signal.h> #endif +#ifdef WIN32 + #include <process.h> +#endif + #include <map> #ifdef HAVE_SYSCALL #include <sys/syscall.h> #endif -#include "autogen_BackupProtocol.h" #include "BackupClientCryptoKeys.h" +#include "BackupClientContext.h" #include "BackupClientFileAttributes.h" +#include "BackupClientInodeToIDMap.h" #include "BackupClientRestore.h" #include "BackupDaemon.h" #include "BackupDaemonConfigVerify.h" +#include "BackupProtocol.h" #include "BackupQueries.h" #include "BackupStoreAccounts.h" #include "BackupStoreConstants.h" @@ -56,6 +61,7 @@ #include "BackupStoreDirectory.h" #include "BackupStoreException.h" #include "BackupStoreConfigVerify.h" +#include "BackupStoreFileEncodeStream.h" #include "BoxPortsAndFiles.h" #include "BoxTime.h" #include "BoxTimeToUnix.h" @@ -67,11 +73,13 @@ #include "intercept.h" #include "IOStreamGetLine.h" #include "LocalProcessStream.h" +#include "MemBlockStream.h" #include "RaidFileController.h" #include "SSLLib.h" #include "ServerControl.h" #include "Socket.h" #include "SocketStreamTLS.h" +#include "StoreTestUtils.h" #include "TLSContext.h" #include "Test.h" #include "Timer.h" @@ -85,16 +93,16 @@ #endif // two cycles and a bit -#define TIME_TO_WAIT_FOR_BACKUP_OPERATION 12 +#define TIME_TO_WAIT_FOR_BACKUP_OPERATION 12 +#define SHORT_TIMEOUT 5000 +#define BACKUP_ERROR_DELAY_SHORTENED 10 +#define DEFAULT_BBACKUPD_CONFIG_FILE "testfiles/bbackupd.conf" void wait_for_backup_operation(const char* message) { wait_for_operation(TIME_TO_WAIT_FOR_BACKUP_OPERATION, message); } -int bbstored_pid = 0; -int bbackupd_pid = 0; - #ifdef HAVE_SYS_XATTR_H bool readxattr_into_map(const char *filename, std::map<std::string,std::string> &rOutput) { @@ -103,6 +111,16 @@ bool readxattr_into_map(const char *filename, std::map<std::string,std::string> ssize_t xattrNamesBufferSize = llistxattr(filename, NULL, 0); if(xattrNamesBufferSize < 0) { +#if HAVE_DECL_ENOTSUP + if(errno == ENOTSUP) + { + // Pretend that it worked, leaving an empty map, so + // that the rest of the attribute comparison will + // proceed as normal. + return true; + } +#endif // HAVE_DECL_ENOTSUP + return false; } else if(xattrNamesBufferSize > 0) @@ -306,8 +324,187 @@ bool attrmatch(const char *f1, const char *f2) return (s1.st_mode == s2.st_mode && s1.st_uid == s2.st_uid && s1.st_gid == s2.st_gid); } -int test_basics() +bool unpack_files(const std::string& archive_file, + const std::string& destination_dir = "testfiles", + const std::string& tar_options = "") +{ + BOX_INFO("Unpacking test fixture archive into " << destination_dir + << ": " << archive_file); + +#ifdef _MSC_VER // No tar, use 7zip. + // 7za only extracts the tgz file to a tar file, which we have to extract in a + // separate step. + std::string cmd = std::string("7za x testfiles/") + archive_file + ".tgz -aos " + "-otestfiles >nul:"; + TEST_LINE_OR(::system(cmd.c_str()) == 0, cmd, return false); + + cmd = std::string("7za x testfiles/") + archive_file + ".tar -aos " + "-o" + destination_dir + " -x!.\\TestDir1\\symlink? -x!.\\test2 >nul:"; +#elif defined WIN32 // Cygwin + MinGW, we can use real tar. + std::string cmd("tar xz"); + cmd += tar_options + " -f testfiles/" + archive_file + ".tgz " + + "-C " + destination_dir; +#else // Unixish, but Solaris tar doesn't like decompressing gzip files. + std::string cmd("gzip -d < testfiles/"); + cmd += archive_file + ".tgz | ( cd " + destination_dir + " && tar xf" + + tar_options + " -)"; +#endif + + TEST_LINE_OR(::system(cmd.c_str()) == 0, cmd, return false); + return true; +} + +Daemon* spDaemon = NULL; + +bool configure_bbackupd(BackupDaemon& bbackupd, const std::string& config_file) +{ + // Stop bbackupd initialisation from changing the console logging level + // and the program name tag. + Logger& console(Logging::GetConsole()); + Logger::LevelGuard undo_log_level_change(console, console.GetLevel()); + Logging::Tagger undo_program_name_change; + + std::vector<std::string> args; + size_t last_arg_start = 0; + for (size_t pos = 0; pos <= bbackupd_args.size(); pos++) + { + char c; + + if (pos == bbackupd_args.size()) + { + c = ' '; // finish last argument + } + else + { + c = bbackupd_args[pos]; + } + + if (c == ' ') + { + if (last_arg_start < pos) + { + std::string last_arg = + bbackupd_args.substr(last_arg_start, + pos - last_arg_start); + args.push_back(last_arg); + } + + last_arg_start = pos + 1; + } + } + + MemoryBlockGuard<const char **> argv_buffer(sizeof(const char*) * (args.size() + 1)); + const char **argv = argv_buffer; + argv_buffer[0] = "bbackupd"; + for (size_t i = 0; i < args.size(); i++) + { + argv_buffer[i + 1] = args[i].c_str(); + } + + TEST_EQUAL_LINE(0, bbackupd.ProcessOptions(args.size() + 1, argv), + "processing command-line options"); + + bbackupd.Configure(config_file); + bbackupd.InitCrypto(); + + return true; +} + +bool kill_running_daemons() +{ + bool success = true; + + if(FileExists("testfiles/bbstored.pid")) + { + TEST_THAT_OR(KillServer("testfiles/bbstored.pid", true), success = false); + } + + if(FileExists("testfiles/bbackupd.pid")) + { + TEST_THAT_OR(KillServer("testfiles/bbackupd.pid", true), success = false); + } + + return success; +} + +bool setup_test_bbackupd(BackupDaemon& bbackupd, bool do_unpack_files = true, + bool do_start_bbstored = true) +{ + Timers::Cleanup(false); // don't throw exception if not initialised + Timers::Init(); + + if (do_start_bbstored) + { + TEST_THAT_OR(StartServer(), FAIL); + } + + if (do_unpack_files) + { + TEST_THAT_OR(unpack_files("test_base"), FAIL); + // Older versions of GNU tar fail to set the timestamps on + // symlinks, which makes them appear too recent to be backed + // up immediately, causing test_bbackupd_uploads_files() for + // example to fail. So restore the timestamps manually. + // http://lists.gnu.org/archive/html/bug-tar/2009-08/msg00007.html + // http://git.savannah.gnu.org/cgit/tar.git/plain/NEWS?id=release_1_24 + #ifdef HAVE_UTIMENSAT + const struct timespec times[2] = { + {1065707200, 0}, + {1065707200, 0}, + }; + const char * filenames[] = { + "testfiles/TestDir1/symlink1", + "testfiles/TestDir1/symlink2", + "testfiles/TestDir1/symlink3", + NULL, + }; + for (int i = 0; filenames[i] != NULL; i++) + { + TEST_THAT_OR(utimensat(AT_FDCWD, filenames[i], + times, AT_SYMLINK_NOFOLLOW) == 0, + BOX_LOG_SYS_ERROR("Failed to change " + "timestamp on symlink: " << + filenames[i])); + } + #endif + } + + TEST_THAT_OR(configure_bbackupd(bbackupd, "testfiles/bbackupd.conf"), + FAIL); + spDaemon = &bbackupd; + return true; +} + +//! Simplifies calling setUp() with the current function name in each test. +#define SETUP_TEST_BBACKUPD() \ + SETUP(); \ + TEST_THAT(bbackupd_pid == 0 || StopClient()); \ + TEST_THAT(bbstored_pid == 0 || StopServer()); \ + TEST_THAT(kill_running_daemons()); \ + TEST_THAT(create_account(10000, 20000)); + +#define SETUP_WITHOUT_FILES() \ + SETUP_TEST_BBACKUPD(); \ + BackupDaemon bbackupd; \ + TEST_THAT_OR(setup_test_bbackupd(bbackupd, false), FAIL); \ + TEST_THAT_OR(::mkdir("testfiles/TestDir1", 0755) == 0, FAIL); + +#define SETUP_WITH_BBSTORED() \ + SETUP_TEST_BBACKUPD(); \ + BackupDaemon bbackupd; \ + TEST_THAT_OR(setup_test_bbackupd(bbackupd), FAIL); + +#define TEARDOWN_TEST_BBACKUPD() \ + TEST_THAT(bbackupd_pid == 0 || StopClient()); \ + TEST_THAT(bbstored_pid == 0 || StopServer()); \ + TEST_THAT(kill_running_daemons()); \ + TEARDOWN(); + +bool test_basics() { + SETUP_TEST_BBACKUPD(); + TEST_THAT_OR(unpack_files("test_base"), FAIL); + // Read attributes from files BackupClientFileAttributes t1; t1.ReadAttributes("testfiles/test1"); @@ -324,7 +521,7 @@ int test_basics() BackupClientFileAttributes t3; { - Logging::Guard guard(Log::ERROR); + Logger::LevelGuard(Logging::GetConsole(), Log::ERROR); TEST_CHECK_THROWS(t3.ReadAttributes("doesn't exist"), CommonException, OSFileError); } @@ -338,6 +535,8 @@ int test_basics() // Apply attributes to these new files t1.WriteAttributes("testfiles/test1_n"); #ifdef WIN32 + // We can't apply symlink attributes on Win32, so use a normal file's + // attributes instead. t1.WriteAttributes("testfiles/test2_n"); #else t2.WriteAttributes("testfiles/test2_n"); @@ -345,7 +544,7 @@ int test_basics() #ifndef WIN32 { - Logging::Guard guard(Log::ERROR); + Logger::LevelGuard(Logging::GetConsole(), Log::ERROR); TEST_CHECK_THROWS(t1.WriteAttributes("testfiles/test1_nXX"), CommonException, OSFileError); TEST_CHECK_THROWS(t3.WriteAttributes("doesn't exist"), @@ -423,55 +622,10 @@ int test_basics() finish_with_write_xattr_test(); #endif // HAVE_SYS_XATTR_H - return 0; -} - -int test_setupaccount() -{ - TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " - "testfiles/bbstored.conf create 01234567 0 1000B 2000B") == 0); - TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); - return 0; -} - -int test_run_bbstored() -{ - std::string cmd = BBSTORED " " + bbstored_args + - " testfiles/bbstored.conf"; - bbstored_pid = LaunchServer(cmd, "testfiles/bbstored.pid"); - - TEST_THAT(bbstored_pid != -1 && bbstored_pid != 0); - - if(bbstored_pid > 0) - { - ::safe_sleep(1); - TEST_THAT(ServerIsAlive(bbstored_pid)); - return 0; // success - } - - return 1; -} - -int test_kill_bbstored(bool wait_for_process = false) -{ - TEST_THAT(KillServer(bbstored_pid, wait_for_process)); - ::safe_sleep(1); - TEST_THAT(!ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbstored_pid)) - { - bbstored_pid = 0; - } - - #ifdef WIN32 - TEST_THAT(unlink("testfiles/bbstored.pid") == 0); - #else - TestRemoteProcessMemLeaks("bbstored.memleaks"); - #endif - - return 0; + TEARDOWN_TEST_BBACKUPD(); } -int64_t GetDirID(BackupProtocolClient &protocol, const char *name, int64_t InDirectory) +int64_t GetDirID(BackupProtocolCallable &protocol, const char *name, int64_t InDirectory) { protocol.QueryListDirectory( InDirectory, @@ -513,10 +667,11 @@ void do_interrupted_restore(const TLSContext &context, int64_t restoredirid) // child process { // connect and log in - SocketStreamTLS conn; - conn.Open(context, Socket::TypeINET, "localhost", - 22011); - BackupProtocolClient protocol(conn); + SocketStreamTLS* pConn = new SocketStreamTLS; + std::auto_ptr<SocketStream> apConn(pConn); + pConn->Open(context, Socket::TypeINET, "localhost", 22011); + BackupProtocolClient protocol(apConn); + protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, @@ -579,7 +734,7 @@ void do_interrupted_restore(const TLSContext &context, int64_t restoredirid) #endif // !WIN32 #ifdef WIN32 -bool set_file_time(const char* filename, FILETIME creationTime, +bool set_file_time(const char* filename, FILETIME creationTime, FILETIME lastModTime, FILETIME lastAccessTime) { HANDLE handle = openfile(filename, O_RDWR, 0); @@ -595,7 +750,7 @@ bool set_file_time(const char* filename, FILETIME creationTime, } #endif -void intercept_setup_delay(const char *filename, unsigned int delay_after, +void intercept_setup_delay(const char *filename, unsigned int delay_after, int delay_ms, int syscall_to_delay); bool intercept_triggered(); @@ -603,7 +758,7 @@ int64_t SearchDir(BackupStoreDirectory& rDir, const std::string& rChildName) { BackupStoreDirectory::Iterator i(rDir); - BackupStoreFilenameClear child(rChildName.c_str()); + BackupStoreFilenameClear child(rChildName); BackupStoreDirectory::Entry *en = i.FindMatchingClearName(child); if (en == 0) return 0; int64_t id = en->GetObjectID(); @@ -612,50 +767,18 @@ int64_t SearchDir(BackupStoreDirectory& rDir, return id; } -SocketStreamTLS sSocket; - -std::auto_ptr<BackupProtocolClient> Connect(TLSContext& rContext) -{ - sSocket.Open(rContext, Socket::TypeINET, - "localhost", 22011); - std::auto_ptr<BackupProtocolClient> connection; - connection.reset(new BackupProtocolClient(sSocket)); - connection->Handshake(); - std::auto_ptr<BackupProtocolVersion> - serverVersion(connection->QueryVersion( - BACKUP_STORE_SERVER_VERSION)); - if(serverVersion->GetVersion() != - BACKUP_STORE_SERVER_VERSION) - { - THROW_EXCEPTION(BackupStoreException, - WrongServerVersion); - } - return connection; -} - -std::auto_ptr<BackupProtocolClient> ConnectAndLogin(TLSContext& rContext, - int flags) -{ - std::auto_ptr<BackupProtocolClient> connection(Connect(rContext)); - connection->QueryLogin(0x01234567, flags); - return connection; -} - std::auto_ptr<BackupStoreDirectory> ReadDirectory ( - BackupProtocolClient& rClient, + BackupProtocolCallable& rClient, int64_t id = BackupProtocolListDirectory::RootDirectory ) { std::auto_ptr<BackupProtocolSuccess> dirreply( rClient.QueryListDirectory(id, false, 0, false)); - std::auto_ptr<IOStream> dirstream(rClient.ReceiveStream()); - std::auto_ptr<BackupStoreDirectory> apDir(new BackupStoreDirectory()); - apDir->ReadFromStream(*dirstream, rClient.GetTimeout()); + std::auto_ptr<BackupStoreDirectory> apDir( + new BackupStoreDirectory(rClient.ReceiveStream(), SHORT_TIMEOUT)); return apDir; } - -Daemon* spDaemon = NULL; int start_internal_daemon() { @@ -737,6 +860,8 @@ static int readdir_stop_time = 0; static char stat_hook_filename[512]; // First test hook, during the directory scanning stage, returns empty. +// (Where is this stage? I can't find it, so I switched from using +// readdir_test_hook_1 to readdir_test_hook_2 in intercept tests.) // This will not match the directory on the store, so a sync will start. // We set up the next intercept for the same directory by passing NULL. @@ -756,8 +881,9 @@ extern "C" struct dirent *readdir_test_hook_1(DIR *dir) return NULL; } -// Second test hook, during the directory sync stage, keeps returning -// new filenames until the timer expires, then disables the intercept. +// Second test hook, called by BackupClientDirectoryRecord::SyncDirectory, +// keeps returning new filenames until the timer expires, then disables the +// intercept. extern "C" struct dirent *readdir_test_hook_2(DIR *dir) { @@ -809,7 +935,10 @@ extern "C" struct dirent *readdir_test_hook_2(DIR *dir) #endif // sleep a bit to reduce the number of dirents returned - ::safe_sleep(1); + if (time_now < readdir_stop_time) + { + ::safe_sleep(1); + } return &readdir_test_dirent; } @@ -839,497 +968,665 @@ int lstat_test_post_hook(int old_ret, const char *file_name, struct stat *buf) return old_ret; } -bool test_entry_deleted(BackupStoreDirectory& rDir, +bool test_entry_deleted(BackupStoreDirectory& rDir, const std::string& rName) { BackupStoreDirectory::Iterator i(rDir); BackupStoreDirectory::Entry *en = i.FindMatchingClearName( BackupStoreFilenameClear(rName)); - TEST_THAT(en != 0); - if (en == 0) return false; + TEST_THAT_OR(en != 0, return false); int16_t flags = en->GetFlags(); - TEST_THAT(flags && BackupStoreDirectory::Entry::Flags_Deleted); + TEST_LINE(flags && BackupStoreDirectory::Entry::Flags_Deleted, + rName + " should have been deleted"); return flags && BackupStoreDirectory::Entry::Flags_Deleted; } -bool compare_all(BackupQueries::ReturnCode::Type expected_status, - std::string config_file = "testfiles/bbackupd.conf") +bool compare(BackupQueries::ReturnCode::Type expected_status, + const std::string& bbackupquery_options = "", + const std::string& compare_options = "-acQ") { std::string cmd = BBACKUPQUERY; cmd += " "; cmd += (expected_status == BackupQueries::ReturnCode::Compare_Same) - ? "-Werror" : "-Wwarning"; - cmd += " -c "; - cmd += config_file; - cmd += " \"compare -acQ\" quit"; + ? "-Wwarning" : "-Werror"; + cmd += " -c testfiles/bbackupd.conf "; + cmd += " " + bbackupquery_options; + cmd += " \"compare " + compare_options + "\" quit"; int returnValue = ::system(cmd.c_str()); - int expected_system_result = (int) expected_status; - #ifndef WIN32 - expected_system_result <<= 8; - #endif +#ifndef WIN32 + expected_system_result <<= 8; +#endif TEST_EQUAL_LINE(expected_system_result, returnValue, "compare return value"); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); return (returnValue == expected_system_result); } -#define TEST_COMPARE(...) \ - TEST_THAT(compare_all(BackupQueries::ReturnCode::__VA_ARGS__)); +bool compare_local(BackupQueries::ReturnCode::Type expected_status, + BackupProtocolCallable& client, + const std::string& compare_options = "acQ") +{ + std::auto_ptr<Configuration> config = + load_config_file(DEFAULT_BBACKUPD_CONFIG_FILE, BackupDaemonConfigVerify); + TEST_THAT_OR(config.get(), return false); + BackupQueries bbackupquery(client, *config, false); + + std::vector<std::string> args; + bool opts[256] = {}; + for (std::string::const_iterator i = compare_options.begin(); + i != compare_options.end(); i++) + { + opts[(unsigned char)*i] = true; + } + bbackupquery.CommandCompare(args, opts); + TEST_EQUAL_OR(expected_status, bbackupquery.GetReturnCode(), + return false); + return true; +} -int test_bbackupd() +bool bbackupquery(const std::string& arguments, + const std::string& memleaks_file = "bbackupquery.memleaks") { - // First, wait for a normal period to make sure the last changes - // attributes are within a normal backup timeframe. - // wait_for_backup_operation(); + std::string cmd = BBACKUPQUERY; + cmd += " -c testfiles/bbackupd.conf " + arguments + " quit"; - // Connection gubbins - TLSContext context; - context.Initialise(false /* client */, - "testfiles/clientCerts.pem", - "testfiles/clientPrivKey.pem", - "testfiles/clientTrustedCAs.pem"); + int returnValue = ::system(cmd.c_str()); - printf("\n==== Testing that ReadDirectory on nonexistent directory " - "does not crash\n"); - { - std::auto_ptr<BackupProtocolClient> client = ConnectAndLogin( - context, 0 /* read-write */); - - { - Logging::Guard guard(Log::ERROR); - TEST_CHECK_THROWS(ReadDirectory(*client, 0x12345678), - ConnectionException, - Conn_Protocol_UnexpectedReply); - } +#ifndef WIN32 + returnValue >>= 8; +#endif - client->QueryFinished(); - sSocket.Close(); - } + TestRemoteProcessMemLeaks(memleaks_file.c_str()); + TEST_EQUAL(returnValue, BackupQueries::ReturnCode::Command_OK); + return (returnValue == BackupQueries::ReturnCode::Command_OK); +} - // unpack the files for the initial test - TEST_THAT(::system("rm -rf testfiles/TestDir1") == 0); - TEST_THAT(::mkdir("testfiles/TestDir1", 0777) == 0); +bool restore(const std::string& location, const std::string& dest_dir) +{ + std::string cmd = "\"restore " + location + " " + dest_dir + "\""; + TEST_THAT_OR(bbackupquery(cmd), FAIL); + return true; +} - #ifdef WIN32 - TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz " - "-C testfiles/TestDir1") == 0); - #else - TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz " - "| ( cd testfiles/TestDir1 && tar xf - )") == 0); - #endif +bool touch_and_wait(const std::string& filename) +{ + int fd = open(filename.c_str(), O_CREAT | O_WRONLY, 0755); + TEST_THAT(fd > 0); + if (fd <= 0) return false; -#ifdef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE - printf("\n==== Skipping intercept-based KeepAlive tests " - "on this platform.\n"); -#else - printf("\n==== Testing SSL KeepAlive messages\n"); + // write again, to update the file's timestamp + int write_result = write(fd, "z", 1); + TEST_EQUAL_LINE(1, write_result, "Buffer write"); + if (write_result != 1) return false; - { - #ifdef WIN32 - #error TODO: implement threads on Win32, or this test \ - will not finish properly - #endif + TEST_THAT(close(fd) == 0); - // bbackupd daemon will try to initialise timers itself - Timers::Cleanup(); - - // something to diff against (empty file doesn't work) - int fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); - TEST_THAT(fd > 0); + // wait long enough to put file into sync window + wait_for_operation(5, "locally modified file to " + "mature for sync"); + return true; +} - char buffer[10000]; - memset(buffer, 0, sizeof(buffer)); +TLSContext sTlsContext; - TEST_EQUAL_LINE(sizeof(buffer), - write(fd, buffer, sizeof(buffer)), - "Buffer write"); - TEST_THAT(close(fd) == 0); - - int pid = start_internal_daemon(); - wait_for_backup_operation("internal daemon to run a sync"); - TEST_THAT(stop_internal_daemon(pid)); - - // two-second delay on the first read() of f1 - // should mean that a single keepalive is sent, - // and diff does not abort. - intercept_setup_delay("testfiles/TestDir1/spacetest/f1", - 0, 2000, SYS_read, 1); - TEST_THAT(unlink("testfiles/bbackupd.log") == 0); - - pid = start_internal_daemon(); - intercept_clear_setup(); - - fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); - TEST_THAT(fd > 0); - // write again, to update the file's timestamp - TEST_EQUAL_LINE(sizeof(buffer), - write(fd, buffer, sizeof(buffer)), - "Buffer write"); - TEST_THAT(close(fd) == 0); +#define TEST_COMPARE(...) \ + TEST_THAT(compare(BackupQueries::ReturnCode::__VA_ARGS__)); +#define TEST_COMPARE_LOCAL(...) \ + TEST_THAT(compare_local(BackupQueries::ReturnCode::__VA_ARGS__)); - wait_for_backup_operation("internal daemon to sync " - "spacetest/f1"); - // can't test whether intercept was triggered, because - // it's in a different process. - // TEST_THAT(intercept_triggered()); - TEST_THAT(stop_internal_daemon(pid)); +bool search_for_file(const std::string& filename) +{ + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, BackupProtocolLogin::Flags_ReadOnly); - // check that keepalive was written to logs, and - // diff was not aborted, i.e. upload was a diff - FileStream fs("testfiles/bbackupd.log", O_RDONLY); - IOStreamGetLine reader(fs); - bool found1 = false; + std::auto_ptr<BackupStoreDirectory> dir = ReadDirectory(*client); + int64_t testDirId = SearchDir(*dir, filename); + client->QueryFinished(); - while (!reader.IsEOF()) - { - std::string line; - TEST_THAT(reader.GetLine(line)); - if (line == "Send GetBlockIndexByName(0x3,\"f1\")") - { - found1 = true; - break; - } - } + return (testDirId != 0); +} - TEST_THAT(found1); - if (found1) - { - std::string line; - TEST_THAT(reader.GetLine(line)); - std::string comp = "Receive Success(0x"; - TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), - line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receiving stream, size 124", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Send GetIsAlive()", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receive IsAlive()", line); +class MockClientContext : public BackupClientContext +{ +public: + BackupProtocolCallable& mrClient; + int mNumKeepAlivesPolled; + int mKeepAliveTime; + + MockClientContext + ( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier, + bool TcpNiceMode, + BackupProtocolCallable& rClient + ) + : BackupClientContext(rResolver, rTLSContext, + rHostname, Port, AccountNumber, ExtendedLogging, + ExtendedLogToFile, ExtendedLogFile, + rProgressNotifier, TcpNiceMode), + mrClient(rClient), + mNumKeepAlivesPolled(0), + mKeepAliveTime(-1) + { } + + BackupProtocolCallable &GetConnection() + { + return mrClient; + } - TEST_THAT(reader.GetLine(line)); - comp = "Send StoreFile(0x3,"; - TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), - line); - comp = ",\"f1\")"; - std::string sub = line.substr(line.size() - comp.size()); - TEST_EQUAL_LINE(comp, sub, line); - std::string comp2 = ",0x0,"; - sub = line.substr(line.size() - comp.size() - - comp2.size() + 1, comp2.size()); - TEST_LINE(comp2 != sub, line); - } + virtual BackupProtocolCallable* GetOpenConnection() const + { + return &mrClient; + } - Timers::Init(); - - // stop early to make debugging easier - if (failures) return 1; - - // four-second delay on first read() of f1 - // should mean that no keepalives were sent, - // because diff was immediately aborted - // before any matching blocks could be found. - intercept_setup_delay("testfiles/TestDir1/spacetest/f1", - 0, 4000, SYS_read, 1); - - { - BackupDaemon bbackupd; - bbackupd.Configure("testfiles/bbackupd.conf"); - bbackupd.InitCrypto(); - - fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); - TEST_THAT(fd > 0); - // write again, to update the file's timestamp - TEST_EQUAL_LINE(1, write(fd, "z", 1), "Buffer write"); - TEST_THAT(close(fd) == 0); + void SetKeepAliveTime(int iSeconds) + { + mKeepAliveTime = iSeconds; + BackupClientContext::SetKeepAliveTime(iSeconds); + } - // wait long enough to put file into sync window - wait_for_operation(5, "locally modified file to " - "mature for sync"); + virtual void DoKeepAlive() + { + mNumKeepAlivesPolled++; + BackupClientContext::DoKeepAlive(); + } +}; - bbackupd.RunSyncNow(); - TEST_THAT(intercept_triggered()); - intercept_clear_setup(); - Timers::Cleanup(); - } +class MockBackupDaemon : public BackupDaemon +{ + BackupProtocolCallable& mrClient; + +public: + MockBackupDaemon(BackupProtocolCallable &rClient) + : mrClient(rClient) + { } + + std::auto_ptr<BackupClientContext> GetNewContext + ( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier, + bool TcpNiceMode + ) + { + std::auto_ptr<BackupClientContext> context( + new MockClientContext(rResolver, + rTLSContext, rHostname, Port, + AccountNumber, ExtendedLogging, + ExtendedLogToFile, ExtendedLogFile, + rProgressNotifier, TcpNiceMode, mrClient)); + return context; + } +}; - // check that the diff was aborted, i.e. upload was not a diff - found1 = false; +bool test_readdirectory_on_nonexistent_dir() +{ + SETUP_WITH_BBSTORED(); - while (!reader.IsEOF()) + { + std::auto_ptr<BackupProtocolCallable> client = connect_and_login( + sTlsContext, 0 /* read-write */); + { - std::string line; - TEST_THAT(reader.GetLine(line)); - if (line == "Send GetBlockIndexByName(0x3,\"f1\")") - { - found1 = true; - break; - } + Logger::LevelGuard(Logging::GetConsole(), Log::ERROR); + TEST_CHECK_THROWS(ReadDirectory(*client, 0x12345678), + ConnectionException, + Protocol_UnexpectedReply); + TEST_PROTOCOL_ERROR_OR(*client, Err_DoesNotExist,); } - TEST_THAT(found1); - if (found1) - { - std::string line; - TEST_THAT(reader.GetLine(line)); - std::string comp = "Receive Success(0x"; - TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), - line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receiving stream, size 124", line); + client->QueryFinished(); + } - // delaying for 4 seconds in one step means that - // the diff timer and the keepalive timer will - // both expire, and the diff timer is honoured first, - // so there will be no keepalives. + TEARDOWN_TEST_BBACKUPD(); +} - TEST_THAT(reader.GetLine(line)); - comp = "Send StoreFile(0x3,"; - TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), - line); - comp = ",0x0,\"f1\")"; - std::string sub = line.substr(line.size() - comp.size()); - TEST_EQUAL_LINE(comp, sub, line); - } +bool test_bbackupquery_parser_escape_slashes() +{ + SETUP_WITH_BBSTORED(); + + BackupProtocolLocal2 connection(0x01234567, "test", + "backup/01234567/", 0, false); + + BackupClientFileAttributes attr; + attr.ReadAttributes("testfiles/TestDir1", + false /* put mod times in the attributes, please */); + std::auto_ptr<IOStream> attrStream(new MemBlockStream(attr)); + BackupStoreFilenameClear dirname("foo"); + int64_t foo_id = connection.QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, // containing directory + 0, // attrModTime, + dirname, // dirname, + attrStream)->GetObjectID(); + + attrStream.reset(new MemBlockStream(attr)); + dirname = BackupStoreFilenameClear("/bar"); + int64_t bar_id = connection.QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, // containing directory + 0, // attrModTime, + dirname, // dirname, + attrStream)->GetObjectID(); + + std::auto_ptr<Configuration> config = + load_config_file(DEFAULT_BBACKUPD_CONFIG_FILE, BackupDaemonConfigVerify); + TEST_THAT_OR(config.get(), return false); + BackupQueries query(connection, *config, false); // read-only + + TEST_EQUAL(foo_id, query.FindDirectoryObjectID("foo")); + TEST_EQUAL(foo_id, query.FindDirectoryObjectID("/foo")); + TEST_EQUAL(0, query.FindDirectoryObjectID("\\/foo")); + TEST_EQUAL(0, query.FindDirectoryObjectID("/bar")); + TEST_EQUAL(bar_id, query.FindDirectoryObjectID("\\/bar")); + connection.QueryFinished(); + + TEARDOWN_TEST_BBACKUPD(); +} - if (failures > 0) +bool test_getobject_on_nonexistent_file() +{ + SETUP_WITH_BBSTORED(); + + { + std::auto_ptr<Configuration> config = + load_config_file(DEFAULT_BBACKUPD_CONFIG_FILE, BackupDaemonConfigVerify); + TEST_THAT_OR(config.get(), return false); + + std::auto_ptr<BackupProtocolCallable> connection = + connect_and_login(sTlsContext, 0 /* read-write */); + BackupQueries query(*connection, *config, false); // read-only + std::vector<std::string> args; + args.push_back("2"); // object ID + args.push_back("testfiles/2.obj"); // output file + bool opts[256]; + + Capture capture; + Logging::TempLoggerGuard guard(&capture); + query.CommandGetObject(args, opts); + std::vector<Capture::Message> messages = capture.GetMessages(); + TEST_THAT(!messages.empty()); + if (!messages.empty()) { - // stop early to make debugging easier - Timers::Init(); - return 1; + std::string last_message = messages.back().message; + TEST_EQUAL("Object ID 0x2 does not exist on store.", + last_message); } - - intercept_setup_delay("testfiles/TestDir1/spacetest/f1", - 0, 1000, SYS_read, 3); - pid = start_internal_daemon(); - intercept_clear_setup(); + } - fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); - TEST_THAT(fd > 0); - // write again, to update the file's timestamp - TEST_EQUAL_LINE(sizeof(buffer), - write(fd, buffer, sizeof(buffer)), - "Buffer write"); - TEST_THAT(close(fd) == 0); + TEARDOWN_TEST_BBACKUPD(); +} + +// ASSERT((mpBlockIndex == 0) || (NumBlocksInIndex != 0)) in +// BackupStoreFileEncodeStream::Recipe::Recipe once failed, apparently because +// a zero byte file had a block index but no entries in it. But this test +// doesn't reproduce the error, so it's not enabled for now. - wait_for_backup_operation("internal daemon to sync " - "spacetest/f1 again"); - // can't test whether intercept was triggered, because - // it's in a different process. - // TEST_THAT(intercept_triggered()); - TEST_THAT(stop_internal_daemon(pid)); +bool test_replace_zero_byte_file_with_nonzero_byte_file() +{ + SETUP_TEST_BBACKUPD(); - // check that the diff was aborted, i.e. upload was not a diff - found1 = false; + TEST_THAT_OR(mkdir("testfiles/TestDir1", 0755) == 0, FAIL); + FileStream emptyFile("testfiles/TestDir1/f2", + O_WRONLY | O_CREAT | O_EXCL, 0755); + wait_for_operation(5, "f2 to be old enough"); - while (!reader.IsEOF()) + BackupProtocolLocal2 client(0x01234567, "test", + "backup/01234567/", 0, false); + MockBackupDaemon bbackupd(client); + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd.conf")); + bbackupd.RunSyncNow(); + TEST_COMPARE_LOCAL(Compare_Same, client); + + MemBlockStream stream("Hello world"); + stream.CopyStreamTo(emptyFile); + emptyFile.Close(); + wait_for_operation(5, "f2 to be old enough"); + + bbackupd.RunSyncNow(); + TEST_COMPARE_LOCAL(Compare_Same, client); + + TEARDOWN_TEST_BBACKUPD(); +} + +// This caused the issue reported by Brendon Baumgartner and described in my +// email to the Box Backup list on Mon, 21 Apr 2014 at 18:44:38. If the +// directory disappears then we used to try to send an empty attributes block +// to the server, which is illegal. +bool test_backup_disappearing_directory() +{ + SETUP_WITH_BBSTORED(); + + class BackupClientDirectoryRecordHooked : public BackupClientDirectoryRecord + { + public: + BackupClientDirectoryRecordHooked(int64_t ObjectID, + const std::string &rSubDirName) + : BackupClientDirectoryRecord(ObjectID, rSubDirName), + mDeletedOnce(false) + { } + bool mDeletedOnce; + bool UpdateItems(SyncParams &rParams, const std::string &rLocalPath, + const std::string &rRemotePath, + const Location& rBackupLocation, + BackupStoreDirectory *pDirOnStore, + std::vector<BackupStoreDirectory::Entry *> &rEntriesLeftOver, + std::vector<std::string> &rFiles, + const std::vector<std::string> &rDirs) { - std::string line; - TEST_THAT(reader.GetLine(line)); - if (line == "Send GetBlockIndexByName(0x3,\"f1\")") + if(!mDeletedOnce) { - found1 = true; - break; + TEST_THAT(::rmdir("testfiles/TestDir1/dir23") == 0); + mDeletedOnce = true; } + + return BackupClientDirectoryRecord::UpdateItems(rParams, + rLocalPath, rRemotePath, rBackupLocation, + pDirOnStore, rEntriesLeftOver, rFiles, rDirs); } + }; - TEST_THAT(found1); - if (found1) - { - std::string line; - TEST_THAT(reader.GetLine(line)); - std::string comp = "Receive Success(0x"; - TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), - line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receiving stream, size 124", line); + BackupClientContext clientContext + ( + bbackupd, // rLocationResolver + sTlsContext, + "localhost", + BOX_PORT_BBSTORED_TEST, + 0x01234567, + false, // ExtendedLogging + false, // ExtendedLogFile + "", // extendedLogFile + bbackupd, // rProgressNotifier + false // TcpNice + ); + + BackupClientInodeToIDMap oldMap, newMap; + oldMap.OpenEmpty(); + newMap.Open("testfiles/test_map.db", false, true); + clientContext.SetIDMaps(&oldMap, &newMap); + + BackupClientDirectoryRecord::SyncParams params( + bbackupd, // rRunStatusProvider, + bbackupd, // rSysadminNotifier, + bbackupd, // rProgressNotifier, + clientContext, + &bbackupd); + params.mSyncPeriodEnd = GetCurrentBoxTime(); + + BackupClientFileAttributes attr; + attr.ReadAttributes("testfiles/TestDir1", + false /* put mod times in the attributes, please */); + std::auto_ptr<IOStream> attrStream(new MemBlockStream(attr)); + BackupStoreFilenameClear dirname("Test1"); + std::auto_ptr<BackupProtocolSuccess> + dirCreate(clientContext.GetConnection().QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, // containing directory + 0, // attrModTime, + dirname, // dirname, + attrStream)); + clientContext.CloseAnyOpenConnection(); + + // Object ID for later creation + int64_t oid = dirCreate->GetObjectID(); + BackupClientDirectoryRecordHooked record(oid, "Test1"); + + TEST_COMPARE(Compare_Different); + + Location fakeLocation; + record.SyncDirectory(params, + BACKUPSTORE_ROOT_DIRECTORY_ID, + "testfiles/TestDir1", // locationPath, + "/whee", // remotePath + fakeLocation); + clientContext.CloseAnyOpenConnection(); + TEST_COMPARE(Compare_Same); - // delaying for 3 seconds in steps of 1 second - // means that the keepalive timer will expire 3 times, - // and on the 3rd time the diff timer will expire too. - // The diff timer is honoured first, so there will be - // only two keepalives. - - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Send GetIsAlive()", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receive IsAlive()", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Send GetIsAlive()", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receive IsAlive()", line); + // Run another backup, check that we haven't got an inconsistent + // state that causes a crash. + record.SyncDirectory(params, + BACKUPSTORE_ROOT_DIRECTORY_ID, + "testfiles/TestDir1", // locationPath, + "/whee", // remotePath + fakeLocation); + clientContext.CloseAnyOpenConnection(); + TEST_COMPARE(Compare_Same); - // but two matching blocks should have been found - // already, so the upload should be a diff. + // Now recreate it and run another backup, check that we haven't got + // an inconsistent state that causes a crash or prevents us from + // creating the directory if it appears later. + TEST_THAT(::mkdir("testfiles/TestDir1/dir23", 0755) == 0); + TEST_COMPARE(Compare_Different); + + record.SyncDirectory(params, + BACKUPSTORE_ROOT_DIRECTORY_ID, + "testfiles/TestDir1", // locationPath, + "/whee", // remotePath + fakeLocation); + clientContext.CloseAnyOpenConnection(); + TEST_COMPARE(Compare_Same); - TEST_THAT(reader.GetLine(line)); - comp = "Send StoreFile(0x3,"; - TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), - line); - comp = ",\"f1\")"; - std::string sub = line.substr(line.size() - comp.size()); - TEST_EQUAL_LINE(comp, sub, line); - std::string comp2 = ",0x0,"; - sub = line.substr(line.size() - comp.size() - - comp2.size() + 1, comp2.size()); - TEST_LINE(comp2 != sub, line); - } + TEARDOWN_TEST_BBACKUPD(); +} - // Check that no read error has been reported yet - TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); +class KeepAliveBackupProtocolLocal : public BackupProtocolLocal2 +{ +public: + int mNumKeepAlivesSent; + int mNumKeepAlivesReceived; + +public: + KeepAliveBackupProtocolLocal(int32_t AccountNumber, + const std::string& ConnectionDetails, + const std::string& AccountRootDir, int DiscSetNumber, + bool ReadOnly) + : BackupProtocolLocal2(AccountNumber, ConnectionDetails, AccountRootDir, + DiscSetNumber, ReadOnly), + mNumKeepAlivesSent(0), + mNumKeepAlivesReceived(0) + { } + + std::auto_ptr<BackupProtocolIsAlive> Query(const BackupProtocolGetIsAlive &rQuery) + { + mNumKeepAlivesSent++; + std::auto_ptr<BackupProtocolIsAlive> response = + BackupProtocolLocal::Query(rQuery); + mNumKeepAlivesReceived++; + return response; + } +}; - if (failures > 0) - { - // stop early to make debugging easier - Timers::Init(); - return 1; - } +bool test_ssl_keepalives() +{ + SETUP_TEST_BBACKUPD(); - intercept_setup_readdir_hook("testfiles/TestDir1/spacetest/d1", - readdir_test_hook_1); - - // time for at least two keepalives - readdir_stop_time = time(NULL) + 12 + 2; + KeepAliveBackupProtocolLocal connection(0x01234567, "test", "backup/01234567/", + 0, false); + MockBackupDaemon bbackupd(connection); + TEST_THAT_OR(setup_test_bbackupd(bbackupd), FAIL); - pid = start_internal_daemon(); - intercept_clear_setup(); + // Test that sending a keepalive actually works, when the timeout has expired, + // but doesn't send anything at the beginning: + { + MockClientContext context( + bbackupd, // rResolver + sTlsContext, // rTLSContext + "localhost", // rHostname + BOX_PORT_BBSTORED_TEST, + 0x01234567, // AccountNumber + false, // ExtendedLogging + false, // ExtendedLogToFile + "", // ExtendedLogFile + bbackupd, // rProgressNotifier + false, // TcpNiceMode + connection); // rClient - std::string touchfile = - "testfiles/TestDir1/spacetest/d1/touch-me"; + // Set the timeout to 1 second + context.SetKeepAliveTime(1); + + // Check that DoKeepAlive() does nothing right now + context.DoKeepAlive(); + TEST_EQUAL(0, connection.mNumKeepAlivesSent); + + // Sleep until just before the timer expires, check that DoKeepAlive() + // still does nothing. + ShortSleep(MilliSecondsToBoxTime(900), true); + context.DoKeepAlive(); + TEST_EQUAL(0, connection.mNumKeepAlivesSent); + + // Sleep until just after the timer expires, check that DoKeepAlive() + // sends a GetIsAlive message now. + ShortSleep(MilliSecondsToBoxTime(200), true); + context.DoKeepAlive(); + TEST_EQUAL(1, connection.mNumKeepAlivesSent); + TEST_EQUAL(1, connection.mNumKeepAlivesReceived); + TEST_EQUAL(3, context.mNumKeepAlivesPolled); + } - fd = open(touchfile.c_str(), O_CREAT | O_WRONLY, 0700); - TEST_THAT(fd > 0); - // write again, to update the file's timestamp - TEST_EQUAL_LINE(sizeof(buffer), - write(fd, buffer, sizeof(buffer)), - "Buffer write"); - TEST_THAT(close(fd) == 0); + // Do the initial backup. There are no existing files to diff, so the only + // keepalives polled should be the ones for each directory entry while reading + // directories, and the one in UpdateItems(), which is also once per item (file + // or directory). test_base.tgz has 16 directory entries, so we expect 2 * 16 = 32 + // keepalives in total. Except on Windows where there are no symlinks, and when + // compiled with MSVC we exclude them from the tar extract operation as 7za + // complains about them, so there should be 3 files less, and thus only 26 + // keepalives. +#ifdef _MSC_VER + #define NUM_KEEPALIVES_BASE 26 +#else + #define NUM_KEEPALIVES_BASE 32 +#endif - wait_for_backup_operation("internal daemon to scan " - "spacetest/d1"); - // can't test whether intercept was triggered, because - // it's in a different process. - // TEST_THAT(intercept_triggered()); - TEST_THAT(stop_internal_daemon(pid)); + std::auto_ptr<BackupClientContext> apContext = bbackupd.RunSyncNow(); + MockClientContext* pContext = (MockClientContext *)(apContext.get()); + TEST_EQUAL(NUM_KEEPALIVES_BASE, pContext->mNumKeepAlivesPolled); + TEST_EQUAL(1, pContext->mKeepAliveTime); + + // Calculate the number of blocks that will be in ./TestDir1/x1/dsfdsfs98.fd, + // which is 4269 bytes long. + int64_t NumBlocks; + int32_t BlockSize, LastBlockSize; + BackupStoreFileEncodeStream::CalculateBlockSizes(4269, NumBlocks, BlockSize, LastBlockSize); + TEST_EQUAL(4096, BlockSize); + TEST_EQUAL(173, LastBlockSize); + TEST_EQUAL(2, NumBlocks); + + // Now modify the file and run another backup. It's the only file that should be + // diffed, and DoKeepAlive() should be called for each block size in the original + // file, times the number of times that block size fits into the new file, + // i.e. 1 + (4269 / 256) = 18 times (plus the same 32 while scanning, as above). - // check that keepalives were sent during the dir search - found1 = false; + { + int fd = open("testfiles/TestDir1/x1/dsfdsfs98.fd", O_WRONLY); + TEST_THAT_OR(fd > 0, FAIL); - // skip to next login - while (!reader.IsEOF()) - { - std::string line; - TEST_THAT(reader.GetLine(line)); - if (line == "Send ListDirectory(0x3,0xffff,0xc,true)") - { - found1 = true; - break; - } - } + char buffer[4000]; + memset(buffer, 0, sizeof(buffer)); - TEST_THAT(found1); - if (found1) - { - found1 = false; + TEST_EQUAL_LINE(sizeof(buffer), + write(fd, buffer, sizeof(buffer)), + "Buffer write"); + TEST_THAT(close(fd) == 0); - while (!reader.IsEOF()) - { - std::string line; - TEST_THAT(reader.GetLine(line)); - if (line == "Send ListDirectory(0x3,0xffffffff,0xc,true)") - { - found1 = true; - break; - } - } - } + wait_for_operation(5, "modified file to be old enough"); + } - if (found1) - { - std::string line; - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receive Success(0x3)", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receiving stream, size 425", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Send GetIsAlive()", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receive IsAlive()", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Send GetIsAlive()", line); - TEST_THAT(reader.GetLine(line)); - TEST_EQUAL("Receive IsAlive()", line); - } + apContext = bbackupd.RunSyncNow(); + pContext = (MockClientContext *)(apContext.get()); + TEST_EQUAL(NUM_KEEPALIVES_BASE + (4269/4096) + (4269/173), + pContext->mNumKeepAlivesPolled); + TEARDOWN_TEST_BBACKUPD(); +} - if (failures > 0) - { - // stop early to make debugging easier - Timers::Init(); - return 1; - } +bool test_backup_hardlinked_files() +{ + SETUP_WITH_BBSTORED(); - TEST_THAT(unlink(touchfile.c_str()) == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); - // restore timers for rest of tests - Timers::Init(); - } -#endif // PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + // Create some hard links. First in the same directory: + TEST_THAT(link("testfiles/TestDir1/x1/dsfdsfs98.fd", + "testfiles/TestDir1/x1/hardlink1") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); - // Check that no read error has been reported yet - TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + // Now in a different directory + TEST_THAT(mkdir("testfiles/TestDir1/x2", 0755) == 0); + TEST_THAT(link("testfiles/TestDir1/x1/dsfdsfs98.fd", + "testfiles/TestDir1/x2/hardlink2") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); - std::string cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd.conf"; + // Now delete one of them + TEST_THAT(unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - ::safe_sleep(1); + // And another. + TEST_THAT(unlink("testfiles/TestDir1/x1/hardlink1") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} - if(bbackupd_pid > 0) - { - printf("\n==== Testing that backup pauses when " - "store is full\n"); +bool test_backup_pauses_when_store_is_full() +{ + SETUP_WITHOUT_FILES(); + unpack_files("spacetest1", "testfiles/TestDir1"); + TEST_THAT_OR(StartClient(), FAIL); + // TODO FIXME dedent + { // wait for files to be uploaded - BOX_TRACE("Waiting for all outstanding files to be uploaded") + BOX_TRACE("Waiting for all outstanding files to be uploaded...") wait_for_sync_end(); - BOX_TRACE("done.") + BOX_TRACE("Done. Comparing to check that it worked...") + TEST_COMPARE(Compare_Same); + + // BLOCK + { + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0 /* read-write */); + TEST_THAT(check_num_files(5, 0, 0, 9)); + TEST_THAT(check_num_blocks(*client, 10, 0, 0, 18, 28)); + client->QueryFinished(); + } - // Set limit to something very small - // 26 blocks will be used at this point. - // (12 files + location * 2 for raidfile) - // 20 is what we'll need in a minute + // Set limit to something very small. + // 28 blocks are used at this point. // set soft limit to 0 to ensure that all deleted files // are deleted immediately by housekeeping - TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " - "testfiles/bbstored.conf setlimit 01234567 0B 20B") - == 0); - TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + TEST_THAT(change_account_limits("0B", "20B")); // Unpack some more files - #ifdef WIN32 - TEST_THAT(::system("tar xzvf testfiles/spacetest2.tgz " - "-C testfiles/TestDir1") == 0); - #else - TEST_THAT(::system("gzip -d < testfiles/spacetest2.tgz " - "| ( cd testfiles/TestDir1 && tar xf - )") == 0); - #endif + unpack_files("spacetest2", "testfiles/TestDir1"); // Delete a file and a directory TEST_THAT(::unlink("testfiles/TestDir1/spacetest/f1") == 0); +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\spacetest\\d7") == 0); +#else TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d7") == 0); +#endif - // The following files should be on the server: + // The following files should be in the backup directory: // 00000001 -d---- 00002 (root) // 00000002 -d---- 00002 Test1 // 00000003 -d---- 00002 Test1/spacetest @@ -1339,22 +1636,26 @@ int test_bbackupd() // 00000007 f----- 00002 Test1/spacetest/d1/f3 // 00000008 f----- 00002 Test1/spacetest/d1/f4 // 00000009 -d---- 00002 Test1/spacetest/d2 + // 00000009 -d---- 00002 Test1/spacetest/d6 // 0000000a -d---- 00002 Test1/spacetest/d3 // 0000000b -d---- 00002 Test1/spacetest/d3/d4 // 0000000c f----- 00002 Test1/spacetest/d3/d4/f5 // 0000000d -d---- 00002 Test1/spacetest/d6 + // 0000000d -d---- 00002 Test1/spacetest/d6/d8 + // 0000000d -d---- 00002 Test1/spacetest/d6/d8/f7 // 0000000e -dX--- 00002 Test1/spacetest/d7 - // This is 28 blocks total, of which 2 in deleted files + // + // root + location + spacetest1 + spacetest2 = 17 files + // = 34 blocks with raidfile. Of which 2 in deleted files // and 18 in directories. Note that f1 and d7 may or may // not be deleted yet. // - // spacetest1 + spacetest2 = 16 files = 32 blocks with raidfile - // minus one file and one dir is 28 blocks - // - // d2/f6, d6/d8 and d6/d8/f7 are new - // even if the client marks f1 and d7 as deleted, and - // housekeeping deleted them, the backup cannot complete - // if the limit is 20 blocks. + // The files and dirs from spacetest1 are already on the server + // (28 blocks). If we set the limit to 20 then the client will + // notice as soon as it tries to create the new files and dirs + // from spacetest2. It should still delete f1 and d7, but that + // won't bring it back under the hard limit, so no files from + // spacetest2 should be uploaded. BOX_TRACE("Waiting for sync for bbackupd to notice that the " "store is full"); @@ -1363,82 +1664,144 @@ int test_bbackupd() BOX_TRACE("Compare to check that there are differences"); TEST_COMPARE(Compare_Different); - BOX_TRACE("Compare finished."); // Check that the notify script was run TEST_THAT(TestFileExists("testfiles/notifyran.store-full.1")); // But only once! TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); - - // Kill the daemon - terminate_bbackupd(bbackupd_pid); - wait_for_operation(5, "housekeeping to remove the " - "deleted files"); - - // This removes f1 and d7, which were previously marked - // as deleted, so total usage drops by 4 blocks to 24. + // We can't guarantee to get in before housekeeping runs, so it's safer + // (more reliable) to wait for it to finish. But we also need to stop the + // client first, otherwise it might be connected when housekeeping tries + // to run, and that would prevent it from running, causing test to fail. + TEST_THAT_OR(StopClient(), FAIL); + wait_for_operation(5, "housekeeping to run"); // BLOCK { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, 0 /* read-write */); - - std::auto_ptr<BackupProtocolAccountUsage> usage( - client->QueryGetAccountUsage()); - TEST_EQUAL_LINE(24, usage->GetBlocksUsed(), - "blocks used"); - TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), - "deleted blocks"); - TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(), - "directory blocks"); + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, + BackupProtocolLogin::Flags_ReadOnly); + std::auto_ptr<BackupStoreDirectory> root_dir = + ReadDirectory(*client, BACKUPSTORE_ROOT_DIRECTORY_ID); + int64_t test_dir_id = SearchDir(*root_dir, "Test1"); + TEST_THAT_OR(test_dir_id, FAIL); + std::auto_ptr<BackupStoreDirectory> test_dir = + ReadDirectory(*client, test_dir_id); + + int64_t spacetest_dir_id = SearchDir(*test_dir, "spacetest"); + TEST_THAT_OR(spacetest_dir_id, FAIL); + std::auto_ptr<BackupStoreDirectory> spacetest_dir = + ReadDirectory(*client, spacetest_dir_id); + + int64_t d2_id = SearchDir(*spacetest_dir, "d2"); + int64_t d6_id = SearchDir(*spacetest_dir, "d6"); + TEST_THAT_OR(d2_id != 0, FAIL); + TEST_THAT_OR(d6_id != 0, FAIL); + std::auto_ptr<BackupStoreDirectory> d2_dir = + ReadDirectory(*client, d2_id); + std::auto_ptr<BackupStoreDirectory> d6_dir = + ReadDirectory(*client, d6_id); + + // None of the new files should have been uploaded + TEST_EQUAL(SearchDir(*d2_dir, "f6"), 0); + TEST_EQUAL(SearchDir(*d6_dir, "d8"), 0); + + // But f1 and d7 should have been deleted. + TEST_EQUAL(SearchDir(*spacetest_dir, "f1"), 0); + TEST_EQUAL(SearchDir(*spacetest_dir, "d7"), 0); + + TEST_THAT(check_num_files(4, 0, 0, 8)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 16, 24)); client->QueryFinished(); - sSocket.Close(); } + } - if (failures) return 1; + // Increase the limit again, check that all files are backed up on the + // next run. + TEST_THAT(change_account_limits("0B", "34B")); + TEST_THAT_OR(StartClient(), FAIL); + wait_for_sync_end(); + TEST_COMPARE(Compare_Same); - // ensure time is different to refresh the cache - ::safe_sleep(1); + TEARDOWN_TEST_BBACKUPD(); +} - BOX_TRACE("Restart bbackupd with more exclusions"); - // Start again with a new config that excludes d3 and f2, - // and hence also d3/d4 and d3/d4/f5. bbackupd should mark - // them as deleted and housekeeping should clean up, - // making space to upload the new files. - // total required: (13-2-4+3)*2 = 20 blocks - /* - cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd-exclude.conf"; - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - */ - - BackupDaemon bbackupd; +bool test_bbackupd_exclusions() +{ + SETUP_WITHOUT_FILES(); + + TEST_THAT(unpack_files("spacetest1", "testfiles/TestDir1")); + // Delete a file and a directory + TEST_THAT(::unlink("testfiles/TestDir1/spacetest/f1") == 0); +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\spacetest\\d7") == 0); +#else + TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d7") == 0); +#endif + + // We need to be OVER the limit, i.e. >24 blocks, or + // BackupClientContext will mark us over limit immediately on + // connection. + TEST_THAT(change_account_limits("0B", "25B")); + + // Initial run to get the files backed up + { + bbackupd.RunSyncNow(); + TEST_THAT(!bbackupd.StorageLimitExceeded()); + + // BLOCK { - const char* argv[] = { "bbackupd", - bbackupd_args.c_str() }; - TEST_EQUAL_LINE(0, bbackupd.ProcessOptions(2, argv), - "processing command-line options"); + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0 /* read-write */); + TEST_THAT(check_num_files(4, 0, 0, 8)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 16, 24)); + client->QueryFinished(); } + } - bbackupd.Configure("testfiles/bbackupd-exclude.conf"); - bbackupd.InitCrypto(); - BOX_TRACE("done."); + // Create a directory and then try to run a backup. This should try + // to create the directory on the server, fail, and catch the error. + // The directory that we create, spacetest/d6/d8, is included in + // spacetest2.tgz, so we can ignore this for counting files after we + // unpack spacetest2.tgz. + TEST_THAT(::mkdir("testfiles/TestDir1/spacetest/d6/d8", 0755) == 0); + bbackupd.RunSyncNow(); + TEST_THAT(bbackupd.StorageLimitExceeded()); - // Should be marked as deleted by this run - // wait_for_sync_end(); + // BLOCK + { + TEST_THAT(unpack_files("spacetest2", "testfiles/TestDir1")); + bbackupd.RunSyncNow(); + TEST_THAT(bbackupd.StorageLimitExceeded()); + + // BLOCK { - // Logging::Guard guard(Log::ERROR); - bbackupd.RunSyncNow(); + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0 /* read-write */); + TEST_THAT(check_num_files(4, 0, 0, 8)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 16, 24)); + client->QueryFinished(); } + } + // TODO FIXME dedent + { + // Start again with a new config that excludes d3 and f2, + // and hence also d3/d4 and d3/d4/f5. bbackupd should mark + // them as deleted and housekeeping should later clean up, + // making space to upload the new files. + // total required: (13-2-4+3)*2 = 20 blocks + + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd-exclude.conf")); + // Should be marked as deleted by this run. Hold onto the + // BackupClientContext to stop housekeeping from running. + std::auto_ptr<BackupClientContext> apClientContext = + bbackupd.RunSyncNow(); + // Housekeeping has not yet deleted the files, so there's not + // enough space to upload the new ones. TEST_THAT(bbackupd.StorageLimitExceeded()); // Check that the notify script was run @@ -1446,31 +1809,35 @@ int test_bbackupd() // But only twice! // TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.3")); - // All these should be marked as deleted but hopefully - // not removed by housekeeping yet: - // f1 deleted + // All these should be marked as deleted but not removed by + // housekeeping yet: // f2 excluded - // d1 excluded (why?) - // d1/f3 excluded (why?) // d3 excluded // d3/d4 excluded // d3/d4/f5 excluded - // d7 deleted // Careful with timing here, these files will be removed by - // housekeeping the next time it runs. On Win32, housekeeping - // runs immediately after disconnect, but only if enough time - // has elapsed since the last housekeeping. Since the - // backup run closely follows the last one, housekeeping - // should not run afterwards. On other platforms, we want to - // get in immediately after the backup and hope that - // housekeeping doesn't beat us to it. - - BOX_TRACE("Find out whether bbackupd marked files as deleted"); + // housekeeping the next time it runs. We hold onto the client + // context (and hence an open connection) to stop it from + // running for now. + + // But we can't do that on Windows, because bbstored only + // support one simultaneous connection. So we have to hope that + // housekeeping has run recently enough that it doesn't need to + // run again when we disconnect. + + BOX_INFO("Finding out whether bbackupd marked files as deleted"); + + // TODO FIXME dedent { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, 0 /* read-write */); +#ifdef WIN32 + apClientContext.reset(); +#endif + + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, + BackupProtocolLogin::Flags_ReadOnly); - std::auto_ptr<BackupStoreDirectory> rootDir = + std::auto_ptr<BackupStoreDirectory> rootDir = ReadDirectory(*client); int64_t testDirId = SearchDir(*rootDir, "Test1"); @@ -1479,7 +1846,7 @@ int test_bbackupd() std::auto_ptr<BackupStoreDirectory> Test1_dir = ReadDirectory(*client, testDirId); - int64_t spacetestDirId = SearchDir(*Test1_dir, + int64_t spacetestDirId = SearchDir(*Test1_dir, "spacetest"); TEST_THAT(spacetestDirId != 0); @@ -1500,49 +1867,41 @@ int test_bbackupd() TEST_THAT(test_entry_deleted(*spacetest_dir, "d3")); int64_t d3_id = SearchDir(*spacetest_dir, "d3"); - TEST_THAT(d3_id != 0); + TEST_THAT_OR(d3_id != 0, FAIL); std::auto_ptr<BackupStoreDirectory> d3_dir = ReadDirectory(*client, d3_id); TEST_THAT(test_entry_deleted(*d3_dir, "d4")); int64_t d4_id = SearchDir(*d3_dir, "d4"); - TEST_THAT(d4_id != 0); + TEST_THAT_OR(d4_id != 0, FAIL); std::auto_ptr<BackupStoreDirectory> d4_dir = ReadDirectory(*client, d4_id); TEST_THAT(test_entry_deleted(*d4_dir, "f5")); - std::auto_ptr<BackupProtocolAccountUsage> usage( - client->QueryGetAccountUsage()); - TEST_EQUAL_LINE(24, usage->GetBlocksUsed(), - "blocks used"); - TEST_EQUAL_LINE(4, usage->GetBlocksInDeletedFiles(), - "deleted blocks"); - TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(), - "directory blocks"); // d1/f3 and d1/f4 are the only two files on the // server which are not deleted, they use 2 blocks // each, the rest is directories and 2 deleted files - // (f1 and d3/d4/f5) + // (f2 and d3/d4/f5) + TEST_THAT(check_num_files(2, 0, 2, 8)); + TEST_THAT(check_num_blocks(*client, 4, 0, 4, 16, 24)); // Log out. client->QueryFinished(); - sSocket.Close(); } - BOX_TRACE("done."); - - if (failures) return 1; - wait_for_operation(5, "housekeeping to remove the " - "deleted files"); + // Release our BackupClientContext and open connection, and + // force housekeeping to run now. + apClientContext.reset(); + TEST_THAT(run_housekeeping_and_check_account()); - BOX_TRACE("Check that the files were removed"); + BOX_INFO("Checking that the files were removed"); { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, 0 /* read-write */); - - std::auto_ptr<BackupStoreDirectory> rootDir = + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0 /* read-write */); + + std::auto_ptr<BackupStoreDirectory> rootDir = ReadDirectory(*client); int64_t testDirId = SearchDir(*rootDir, "Test1"); @@ -1551,7 +1910,7 @@ int test_bbackupd() std::auto_ptr<BackupStoreDirectory> Test1_dir = ReadDirectory(*client, testDirId); - int64_t spacetestDirId = SearchDir(*Test1_dir, + int64_t spacetestDirId = SearchDir(*Test1_dir, "spacetest"); TEST_THAT(spacetestDirId != 0); @@ -1563,25 +1922,16 @@ int test_bbackupd() TEST_THAT(SearchDir(*spacetest_dir, "d3") == 0); TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0); - std::auto_ptr<BackupProtocolAccountUsage> usage( - client->QueryGetAccountUsage()); - TEST_EQUAL_LINE(16, usage->GetBlocksUsed(), - "blocks used"); - TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), - "deleted blocks"); - TEST_EQUAL_LINE(12, usage->GetBlocksInDirectories(), - "directory blocks"); - // d1/f3 and d1/f4 are the only two files on the - // server, they use 2 blocks each, the rest is - // directories. + // f2, d3, d3/d4 and d3/d4/f5 have been removed. + // The files were counted as deleted files before, the + // deleted directories just as directories. + TEST_THAT(check_num_files(2, 0, 0, 6)); + TEST_THAT(check_num_blocks(*client, 4, 0, 0, 12, 16)); // Log out. client->QueryFinished(); - sSocket.Close(); } - if (failures) return 1; - // Need 22 blocks free to upload everything TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " "testfiles/bbstored.conf setlimit 01234567 0B 22B") @@ -1595,111 +1945,82 @@ int test_bbackupd() // Check that the contents of the store are the same // as the contents of the disc - TEST_COMPARE(Compare_Same, "testfiles/bbackupd-exclude.conf"); + TEST_COMPARE(Compare_Same, "-c testfiles/bbackupd-exclude.conf"); BOX_TRACE("done."); // BLOCK { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, 0 /* read-write */); - - std::auto_ptr<BackupProtocolAccountUsage> usage( - client->QueryGetAccountUsage()); - TEST_EQUAL_LINE(22, usage->GetBlocksUsed(), - "blocks used"); - TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), - "deleted blocks"); - TEST_EQUAL_LINE(14, usage->GetBlocksInDirectories(), - "directory blocks"); + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0 /* read-write */); + + TEST_THAT(check_num_files(4, 0, 0, 7)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 14, 22)); + // d2/f6, d6/d8 and d6/d8/f7 are new // i.e. 2 new files, 1 new directory client->QueryFinished(); - sSocket.Close(); } + } - if (failures) return 1; - - // Put the limit back - TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " - "testfiles/bbstored.conf setlimit 01234567 " - "1000B 2000B") == 0); - TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); - - // Start again with the old config - BOX_TRACE("Restart bbackupd with original configuration"); - // terminate_bbackupd(); - cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd.conf"; - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - ::safe_sleep(1); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - BOX_TRACE("done."); + TEARDOWN_TEST_BBACKUPD(); +} - // unpack the initial files again - #ifdef WIN32 - TEST_THAT(::system("tar xzvf testfiles/test_base.tgz " - "-C testfiles") == 0); - #else - TEST_THAT(::system("gzip -d < testfiles/test_base.tgz " - "| ( cd testfiles && tar xf - )") == 0); - #endif +bool test_bbackupd_uploads_files() +{ + SETUP_WITH_BBSTORED(); - wait_for_backup_operation("bbackupd to upload more files"); - - // Check that the contents of the store are the same - // as the contents of the disc - // (-a = all, -c = give result in return code) - BOX_TRACE("Check that all files were uploaded successfully"); + // TODO FIXME dedent + { + // The files were all unpacked with timestamps in the past, + // so no delay should be needed to make them eligible to be + // backed up. + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - BOX_TRACE("done."); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; } // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); - #ifndef WIN32 // requires fork - printf("\n==== Testing that bbackupd responds correctly to " - "connection failure\n"); + TEARDOWN_TEST_BBACKUPD(); +} - { - // Kill the daemons - terminate_bbackupd(bbackupd_pid); - test_kill_bbstored(); +bool test_bbackupd_responds_to_connection_failure() +{ + SETUP_TEST_BBACKUPD(); + TEST_THAT_OR(unpack_files("test_base"), FAIL); +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); // requires fork +#else // !WIN32 + // TODO FIXME dedent + { // create a new file to force an upload const char* new_file = "testfiles/TestDir1/force-upload-2"; - int fd = open(new_file, - O_CREAT | O_EXCL | O_WRONLY, 0700); - if (fd <= 0) - { - perror(new_file); - } - TEST_THAT(fd > 0); - + int fd = open(new_file, O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT_OR(fd >= 0, + BOX_LOG_SYS_ERROR(BOX_FILE_MESSAGE(new_file, + "failed to create new file")); + FAIL); + const char* control_string = "whee!\n"; TEST_THAT(write(fd, control_string, strlen(control_string)) == (int)strlen(control_string)); close(fd); - // sleep to make it old enough to upload - safe_sleep(4); + wait_for_operation(5, "new file to be old enough"); + // Start a bbstored with a test hook that makes it terminate + // on the first StoreFile command, breaking the connection to + // bbackupd. class MyHook : public BackupStoreContext::TestHook { + public: + int trigger_count; + MyHook() : trigger_count(0) { } + virtual std::auto_ptr<BackupProtocolMessage> StartCommand( const BackupProtocolMessage& rCommand) { @@ -1707,104 +2028,69 @@ int test_bbackupd() BackupProtocolStoreFile::TypeID) { // terminate badly - THROW_EXCEPTION(CommonException, - Internal); + trigger_count++; + THROW_EXCEPTION(ConnectionException, + TLSReadFailed); } return std::auto_ptr<BackupProtocolMessage>(); } }; - MyHook hook; - bbstored_pid = fork(); - - if (bbstored_pid < 0) + class MockBackupProtocolLocal : public BackupProtocolLocal2 { - BOX_LOG_SYS_ERROR("failed to fork()"); - return 1; - } - - if (bbstored_pid == 0) - { - // in fork child - TEST_THAT(setsid() != -1); - - if (!Logging::IsEnabled(Log::TRACE)) - { - Logging::SetGlobalLevel(Log::NOTHING); - } - - // BackupStoreDaemon must be destroyed before exit(), - // to avoid memory leaks being reported. + public: + MyHook hook; + MockBackupProtocolLocal(int32_t AccountNumber, + const std::string& ConnectionDetails, + const std::string& AccountRootDir, int DiscSetNumber, + bool ReadOnly) + : BackupProtocolLocal2(AccountNumber, ConnectionDetails, + AccountRootDir, DiscSetNumber, ReadOnly) { - BackupStoreDaemon bbstored; - bbstored.SetTestHook(hook); - bbstored.SetRunInForeground(true); - bbstored.Main("testfiles/bbstored.conf"); + GetContext().SetTestHook(hook); } + virtual ~MockBackupProtocolLocal() { } + }; - Timers::Cleanup(); // avoid memory leaks - exit(0); - } - - // in fork parent - bbstored_pid = WaitForServerStartup("testfiles/bbstored.pid", - bbstored_pid); + MockBackupProtocolLocal client(0x01234567, "test", + "backup/01234567/", 0, false); + MockBackupDaemon bbackupd(client); + TEST_THAT_OR(setup_test_bbackupd(bbackupd, false, false), FAIL); TEST_THAT(::system("rm -f testfiles/notifyran.store-full.*") == 0); - - // Ignore SIGPIPE so that when the connection is broken, - // the daemon doesn't terminate. - ::signal(SIGPIPE, SIG_IGN); + std::auto_ptr<BackupClientContext> apClientContext; { - Log::Level newLevel = Logging::GetGlobalLevel(); + Console& console(Logging::GetConsole()); + Logger::LevelGuard guard(console); - if (!Logging::IsEnabled(Log::TRACE)) + if (console.GetLevel() < Log::TRACE) { - newLevel = Log::NOTHING; + console.Filter(Log::NOTHING); } - Logging::Guard guard(newLevel); - - BackupDaemon bbackupd; - bbackupd.Configure("testfiles/bbackupd.conf"); - bbackupd.InitCrypto(); - bbackupd.RunSyncNowWithExceptionHandling(); + apClientContext = bbackupd.RunSyncNowWithExceptionHandling(); } - ::signal(SIGPIPE, SIG_DFL); - + // Should only have been triggered once + TEST_EQUAL(1, client.hook.trigger_count); TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1")); TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2")); TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.1")); - - test_kill_bbstored(true); - - if (failures > 0) - { - // stop early to make debugging easier - return 1; - } - - TEST_THAT(test_run_bbstored() == 0); - - cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd.conf"; - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - ::safe_sleep(1); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; } - #endif // !WIN32 +#endif // !WIN32 - #ifndef WIN32 - printf("\n==== Testing that absolute symlinks are not followed " - "during restore\n"); + TEARDOWN_TEST_BBACKUPD(); +} +bool test_absolute_symlinks_not_followed_during_restore() +{ + SETUP_WITH_BBSTORED(); + +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); // requires symlinks +#else + // TODO FIXME dedent { #define SYM_DIR "testfiles" DIRECTORY_SEPARATOR "TestDir1" \ DIRECTORY_SEPARATOR "symlink_test" @@ -1825,26 +2111,24 @@ int test_bbackupd() char buf[PATH_MAX]; TEST_THAT(getcwd(buf, sizeof(buf)) == buf); std::string path = buf; - path += DIRECTORY_SEPARATOR SYM_DIR + // testfiles/TestDir1/symlink_test/a/subdir -> + // testfiles/TestDir1/symlink_test/b/link + path += DIRECTORY_SEPARATOR SYM_DIR DIRECTORY_SEPARATOR "a" DIRECTORY_SEPARATOR "subdir"; - TEST_THAT(symlink(path.c_str(), SYM_DIR + TEST_THAT(symlink(path.c_str(), SYM_DIR DIRECTORY_SEPARATOR "b" DIRECTORY_SEPARATOR "link") == 0); // also test symlink-to-self loop does not break restore TEST_THAT(symlink("self", SYM_DIR "/self") == 0); - wait_for_operation(4, "symlinks to be old enough"); - sync_and_wait(); + wait_for_operation(5, "symlinks to be old enough"); + bbackupd.RunSyncNow(); // Check that the backup was successful, i.e. no differences TEST_COMPARE(Compare_Same); - // now stop bbackupd and update the test file, - // make the original directory unreadable - terminate_bbackupd(bbackupd_pid); - fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a" DIRECTORY_SEPARATOR "subdir" DIRECTORY_SEPARATOR "content", "w"); @@ -1876,89 +2160,23 @@ int test_bbackupd() TEST_EQUAL("after", line); #undef SYM_DIR + } +#endif - /* - // This is not worth testing or fixing. - // - #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE - printf("\n==== Testing that symlinks to other filesystems " - "can be backed up as roots\n"); - - intercept_setup_lstat_post_hook(lstat_test_post_hook); - TEST_THAT(symlink("TestDir1", "testfiles/symlink-to-TestDir1") - == 0); - - struct stat stat_st, lstat_st; - TEST_THAT(stat("testfiles/symlink-to-TestDir1", &stat_st) == 0); - TEST_THAT(lstat("testfiles/symlink-to-TestDir1", &lstat_st) == 0); - TEST_EQUAL_LINE((stat_st.st_dev ^ 0xFFFF), lstat_st.st_dev, - "stat vs lstat"); - - BackupDaemon bbackupd; - bbackupd.Configure("testfiles/bbackupd-symlink.conf"); - bbackupd.InitCrypto(); - bbackupd.RunSyncNow(); - intercept_clear_setup(); - - compareReturnValue = ::system(BBACKUPQUERY " " - "-c testfiles/bbackupd.conf " - "-l testfiles/query0a.log " - "-Wwarning \"compare -acQ\" quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - - // and again using the symlink during compare - compareReturnValue = ::system(BBACKUPQUERY " " - "-c testfiles/bbackupd-symlink.conf " - "-l testfiles/query0a.log " - "-Wwarning \"compare -acQ\" quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - #endif - */ - - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - ::safe_sleep(1); + TEARDOWN_TEST_BBACKUPD(); +} - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - } - #endif // !WIN32 +// Testing that nonexistent locations are backed up if they are created later +bool test_initially_missing_locations_are_not_forgotten() +{ + SETUP_WITH_BBSTORED(); - printf("\n==== Testing that nonexistent locations are backed up " - "if they are created later\n"); - // ensure that the directory does not exist at the start - TEST_THAT(::system("rm -rf testfiles/TestDir2") == 0); + TEST_THAT(!FileExists("testfiles/TestDir2")); + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd-temploc.conf")); // BLOCK { - // Kill the daemon - terminate_bbackupd(bbackupd_pid); - - // Delete any old result marker files - TEST_RETURN(0, system("rm -f testfiles/notifyran.*")); - - // Start it with a config that has a temporary location - // whose path does not exist yet - std::string cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd-temploc.conf"; - - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - TEST_THAT(!TestFileExists("testfiles/notifyran.backup-start.1")); TEST_THAT(!TestFileExists("testfiles/notifyran.backup-start.2")); TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); @@ -1967,7 +2185,7 @@ int test_bbackupd() TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.1")); TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.2")); - sync_and_wait(); + bbackupd.RunSyncNowWithExceptionHandling(); TEST_COMPARE(Compare_Same); TEST_THAT( TestFileExists("testfiles/notifyran.backup-start.1")); @@ -1979,31 +2197,15 @@ int test_bbackupd() TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.2")); // Did it actually get created? Should not have been! - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, - BackupProtocolLogin::Flags_ReadOnly); - - std::auto_ptr<BackupStoreDirectory> dir = - ReadDirectory(*client); - int64_t testDirId = SearchDir(*dir, "Test2"); - TEST_THAT(testDirId == 0); - client->QueryFinished(); - sSocket.Close(); + TEST_THAT_OR(!search_for_file("Test2"), FAIL); } // create the location directory and unpack some files into it TEST_THAT(::mkdir("testfiles/TestDir2", 0777) == 0); - - #ifdef WIN32 - TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz " - "-C testfiles/TestDir2") == 0); - #else - TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz " - "| ( cd testfiles/TestDir2 && tar xf - )") == 0); - #endif + TEST_THAT(unpack_files("spacetest1", "testfiles/TestDir2")); // check that the files are backed up now - sync_and_wait(); + bbackupd.RunSyncNowWithExceptionHandling(); TEST_COMPARE(Compare_Same); TEST_THAT( TestFileExists("testfiles/notifyran.backup-start.2")); @@ -2016,187 +2218,128 @@ int test_bbackupd() TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.3")); // BLOCK - { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, - BackupProtocolLogin::Flags_ReadOnly); - - std::auto_ptr<BackupStoreDirectory> dir = - ReadDirectory(*client, - BackupProtocolListDirectory::RootDirectory); - int64_t testDirId = SearchDir(*dir, "Test2"); - TEST_THAT(testDirId != 0); + TEST_THAT_OR(search_for_file("Test2"), FAIL); + TEARDOWN_TEST_BBACKUPD(); +} - client->QueryFinished(); - sSocket.Close(); - } +bool test_redundant_locations_deleted_on_time() +{ + SETUP_WITH_BBSTORED(); - printf("\n==== Testing that redundant locations are deleted on time\n"); + // create the location directory and unpack some files into it + TEST_THAT(::mkdir("testfiles/TestDir2", 0777) == 0); + TEST_THAT(unpack_files("spacetest1", "testfiles/TestDir2")); - // BLOCK + // Use a daemon with the TestDir2 location configured to back it up + // to the server. { - // Kill the daemon - terminate_bbackupd(bbackupd_pid); - - // Start it again with the normal config (no Test2) - cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf"; - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - - ::safe_sleep(1); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - // Test2 should be deleted after 10 seconds (4 runs) - wait_for_sync_end(); - wait_for_sync_end(); - wait_for_sync_end(); - - // not yet! should still be there - - { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, - BackupProtocolLogin::Flags_ReadOnly); - - std::auto_ptr<BackupStoreDirectory> dir = - ReadDirectory(*client); - int64_t testDirId = SearchDir(*dir, "Test2"); - TEST_THAT(testDirId != 0); + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd-temploc.conf")); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + } - client->QueryFinished(); - sSocket.Close(); - } + // Now use a daemon with no temporary location, which should delete + // it after 10 seconds + { + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd.conf")); - wait_for_sync_end(); + // Initial run to start the countdown to destruction + bbackupd.RunSyncNow(); - // NOW it should be gone + // Not deleted yet! + TEST_THAT(search_for_file("Test2")); - { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, - BackupProtocolLogin::Flags_ReadOnly); - - std::auto_ptr<BackupStoreDirectory> root_dir = - ReadDirectory(*client); + wait_for_operation(9, "just before Test2 should be deleted"); + bbackupd.RunSyncNow(); + TEST_THAT(search_for_file("Test2")); - TEST_THAT(test_entry_deleted(*root_dir, "Test2")); + // Now wait until after it should be deleted + wait_for_operation(2, "just after Test2 should be deleted"); + bbackupd.RunSyncNow(); - client->QueryFinished(); - sSocket.Close(); - } + TEST_THAT(search_for_file("Test2")); + std::auto_ptr<BackupProtocolCallable> client = connect_and_login( + sTlsContext, 0 /* read-write */); + std::auto_ptr<BackupStoreDirectory> root_dir = + ReadDirectory(*client, BACKUPSTORE_ROOT_DIRECTORY_ID); + TEST_THAT(test_entry_deleted(*root_dir, "Test2")); } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - if(bbackupd_pid > 0) - { - // Delete any old result marker files - TEST_RETURN(0, system("rm -f testfiles/notifyran.*")); - - printf("\n==== Check that read-only directories and " - "their contents can be restored.\n"); + TEARDOWN_TEST_BBACKUPD(); +} - int compareReturnValue; +// Check that read-only directories and their contents can be restored. +bool test_read_only_dirs_can_be_restored() +{ + SETUP_WITH_BBSTORED(); + // TODO FIXME dedent + { { #ifdef WIN32 - TEST_THAT(::system("chmod 0555 testfiles/" - "TestDir1/x1") == 0); + TEST_THAT(::system("attrib +r testfiles\\TestDir1\\x1") + == 0); #else TEST_THAT(chmod("testfiles/TestDir1/x1", 0555) == 0); #endif - wait_for_sync_end(); // too new - wait_for_sync_end(); // should be backed up now - - compareReturnValue = ::system(BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"compare -cEQ Test1 testfiles/TestDir1\" " - "quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1 testfiles/TestDir1"); // check that we can restore it - compareReturnValue = ::system(BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"restore Test1 testfiles/restore1\" " - "quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - - // check that it restored properly - compareReturnValue = ::system(BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"compare -cEQ Test1 testfiles/restore1\" " - "quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_THAT(restore("Test1", "testfiles/restore1")); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1 testfiles/restore1"); // Try a restore with just the remote directory name, // check that it uses the same name in the local // directory. TEST_THAT(::mkdir("testfiles/restore-test", 0700) == 0); - - compareReturnValue = ::system(BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"lcd testfiles/restore-test\" " - "\"restore Test1\" " - "quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); - TestRemoteProcessMemLeaks("testfiles/restore-test/" - "bbackupquery.memleaks"); - - // check that it restored properly - compareReturnValue = ::system(BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"compare -cEQ Test1 testfiles/restore-test/Test1\" " - "quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_THAT(bbackupquery("\"lcd testfiles/restore-test\" " + "\"restore Test1\"")); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1 " + "testfiles/restore-test/Test1"); // put the permissions back to sensible values #ifdef WIN32 - TEST_THAT(::system("chmod 0755 testfiles/" - "TestDir1/x1") == 0); - TEST_THAT(::system("chmod 0755 testfiles/" - "restore1/x1") == 0); + TEST_THAT(::system("attrib -r testfiles\\TestDir1\\x1") + == 0); + TEST_THAT(::system("attrib -r testfiles\\restore1\\x1") + == 0); + TEST_THAT(::system("attrib -r testfiles\\restore-test\\" + "Test1\\x1") == 0); #else TEST_THAT(chmod("testfiles/TestDir1/x1", 0755) == 0); TEST_THAT(chmod("testfiles/restore1/x1", 0755) == 0); + TEST_THAT(chmod("testfiles/restore-test/Test1/x1", + 0755) == 0); #endif } + } -#ifdef WIN32 - printf("\n==== Check that filenames in UTF-8 " - "can be backed up\n"); + TEARDOWN_TEST_BBACKUPD(); +} +// Check that filenames in UTF-8 can be backed up +bool test_unicode_filenames_can_be_backed_up() +{ + SETUP_WITH_BBSTORED(); + +#ifndef WIN32 + BOX_NOTICE("skipping test on this platform"); + // requires ConvertConsoleToUtf8() +#else + // TODO FIXME dedent + { // We have no guarantee that a random Unicode string can be // represented in the user's character set, so we go the // other way, taking three random characters from the - // character set and converting them to Unicode. + // character set and converting them to Unicode. Unless the + // console codepage is CP_UTF8, in which case our random + // characters are not valid, so we use the UTF8 version + // of them instead. // // We hope that these characters are valid in most // character sets, but they probably are not in multibyte @@ -2216,7 +2359,8 @@ int test_bbackupd() // accented characters, which are supported in code page // 850. Depending on your locale, YYMV (your yak may vomit). - std::string foreignCharsNative("\x91\x9b\x86"); + std::string foreignCharsNative = (GetConsoleCP() == CP_UTF8) + ? "\xc3\xa6\xc3\xb8\xc3\xa5" : "\x91\x9b\x86"; std::string foreignCharsUnicode; TEST_THAT(ConvertConsoleToUtf8(foreignCharsNative.c_str(), foreignCharsUnicode)); @@ -2260,46 +2404,13 @@ int test_bbackupd() consoleFileName)); // test that bbackupd will let us lcd into the local - // directory using a relative path - std::string command = BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"lcd testfiles/TestDir1/" + systemDirName + "\" " - "quit"; - compareReturnValue = ::system(command.c_str()); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); - - // and back out again - command = BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"lcd testfiles/TestDir1/" + systemDirName + "\" " - "\"lcd ..\" quit"; - compareReturnValue = ::system(command.c_str()); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); + // directory using a relative path, and back out + TEST_THAT(bbackupquery("\"lcd testfiles/TestDir1/" + + systemDirName + "\" \"lcd ..\"")); // and using an absolute path - command = BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"lcd " + cwd + "/testfiles/TestDir1/" + - systemDirName + "\" quit"; - compareReturnValue = ::system(command.c_str()); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); - - // and back out again - command = BBACKUPQUERY " " - "-Wwarning " - "-c testfiles/bbackupd.conf " - "\"lcd " + cwd + "/testfiles/TestDir1/" + - systemDirName + "\" " - "\"lcd ..\" quit"; - compareReturnValue = ::system(command.c_str()); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); + TEST_THAT(bbackupquery("\"lcd " + cwd + "/testfiles/" + + "TestDir1/" + systemDirName + "\" \"lcd ..\"")); { FileStream fs(filepath.c_str(), O_CREAT | O_RDWR); @@ -2309,17 +2420,22 @@ int test_bbackupd() TEST_EQUAL_LINE(12, fs.GetPosition(), "FileStream position"); fs.Close(); + + // Set modtime back in time to allow immediate backup + struct timeval times[2] = {}; + times[1].tv_sec = 1000000000; + TEST_THAT(emu_utimes(filepath.c_str(), times) == 0); } - wait_for_backup_operation("upload of file with unicode name"); + bbackupd.RunSyncNow(); // Compare to check that the file was uploaded TEST_COMPARE(Compare_Same); // Check that we can find it in directory listing { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, 0); + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0); std::auto_ptr<BackupStoreDirectory> dir = ReadDirectory( *client); @@ -2335,11 +2451,10 @@ int test_bbackupd() TEST_THAT(SearchDir(*dir, filename.c_str()) != 0); // Log out client->QueryFinished(); - sSocket.Close(); } // Check that bbackupquery shows the dir in console encoding - command = BBACKUPQUERY " -Wwarning " + std::string command = BBACKUPQUERY " -Wwarning " "-c testfiles/bbackupd.conf " "-q \"list Test1\" quit"; pid_t bbackupquery_pid; @@ -2392,35 +2507,17 @@ int test_bbackupd() // Check that bbackupquery can compare the dir when given // on the command line in system encoding. - command = BBACKUPQUERY " -c testfiles/bbackupd.conf " - "-Wwarning \"compare -cEQ Test1/" + systemDirName + - " testfiles/TestDir1/" + systemDirName + "\" quit"; - - compareReturnValue = ::system(command.c_str()); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/" + systemDirName + + " testfiles/TestDir1/" + systemDirName); // Check that bbackupquery can restore the dir when given // on the command line in system encoding. - command = BBACKUPQUERY " -c testfiles/bbackupd.conf " - "-Wwarning \"restore Test1/" + systemDirName + - " testfiles/restore-" + systemDirName + "\" quit"; - - compareReturnValue = ::system(command.c_str()); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); + TEST_THAT(restore("Test1/" + systemDirName, + "testfiles/restore-" + systemDirName)); // Compare to make sure it was restored properly. - command = BBACKUPQUERY " -c testfiles/bbackupd.conf " - "-Wwarning \"compare -cEQ Test1/" + systemDirName + - " testfiles/restore-" + systemDirName + "\" quit"; - - compareReturnValue = ::system(command.c_str()); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/" + systemDirName + + " testfiles/restore-" + systemDirName); std::string fileToUnlink = "testfiles/restore-" + dirname + "/" + filename; @@ -2428,80 +2525,47 @@ int test_bbackupd() // Check that bbackupquery can get the file when given // on the command line in system encoding. - command = BBACKUPQUERY " -c testfiles/bbackupd.conf " - "-Wwarning \"get Test1/" + systemDirName + "/" + + TEST_THAT(bbackupquery("\"get Test1/" + systemDirName + "/" + systemFileName + " " + "testfiles/restore-" + - systemDirName + "/" + systemFileName + "\" quit"; - - compareReturnValue = ::system(command.c_str()); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + systemDirName + "/" + systemFileName + "\"")); // And after changing directory to a relative path - command = BBACKUPQUERY " -c testfiles/bbackupd.conf " - "-Wwarning " + TEST_THAT(bbackupquery( "\"lcd testfiles\" " "\"cd Test1/" + systemDirName + "\" " + - "\"get " + systemFileName + "\" quit"; - - compareReturnValue = ::system(command.c_str()); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); - TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks"); + "\"get " + systemFileName + "\"")); // cannot overwrite a file that exists, so delete it std::string tmp = "testfiles/" + filename; TEST_THAT(::unlink(tmp.c_str()) == 0); // And after changing directory to an absolute path - command = BBACKUPQUERY " -c testfiles/bbackupd.conf -Wwarning " + TEST_THAT(bbackupquery( "\"lcd " + cwd + "/testfiles\" " "\"cd Test1/" + systemDirName + "\" " + - "\"get " + systemFileName + "\" quit"; - - compareReturnValue = ::system(command.c_str()); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Command_OK); - TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks"); - - // Compare to make sure it was restored properly. - // The Get command does not restore attributes, so - // we must compare without them (-A) to succeed. - command = BBACKUPQUERY " " - "-c testfiles/bbackupd.conf " - "-Wwarning \"compare -cAEQ Test1/" + systemDirName + - " testfiles/restore-" + systemDirName + "\" quit"; + "\"get " + systemFileName + "\"")); - compareReturnValue = ::system(command.c_str()); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - - // Compare without attributes. This should fail. - command = BBACKUPQUERY " " - "-c testfiles/bbackupd.conf " - "-Werror \"compare -cEQ Test1/" + systemDirName + - " testfiles/restore-" + systemDirName + "\" quit"; - compareReturnValue = ::system(command.c_str()); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Different); -#endif // WIN32 + // Compare to make sure it was restored properly. The Get + // command does restore attributes, so we don't need to + // specify the -A option for this to succeed. + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/" + systemDirName + + " testfiles/restore-" + systemDirName); // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } +#endif // WIN32 + + TEARDOWN_TEST_BBACKUPD(); +} - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - printf("\n==== Check that SyncAllowScript is executed and can " - "pause backup\n"); - fflush(stdout); +bool test_sync_allow_script_can_pause_backup() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + // TODO FIXME dedent + { { wait_for_sync_end(); // we now have 3 seconds before bbackupd @@ -2572,7 +2636,6 @@ int test_bbackupd() TEST_THAT(unlink(sync_control_file) == 0); wait_for_sync_start(); long end_time = time(NULL); - long wait_time = end_time - start_time + 2; // should be about 10 seconds @@ -2589,36 +2652,38 @@ int test_bbackupd() // check that backup has run (compare succeeds) TEST_COMPARE(Compare_Same); - - if (failures) return 1; } // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} - printf("\n==== Delete file and update another, " - "create symlink.\n"); - +// Delete file and update another, create symlink. +bool test_delete_update_and_symlink_files() +{ + SETUP_WITH_BBSTORED(); + + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { // Delete a file TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); #ifndef WIN32 // New symlink - TEST_THAT(::symlink("does-not-exist", + TEST_THAT(::symlink("does-not-exist", "testfiles/TestDir1/symlink-to-dir") == 0); #endif // Update a file (will be uploaded as a diff) { - // Check that the file is over the diffing + // Check that the file is over the diffing // threshold in the bbackupd.conf file - TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") + TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") > 1024); // Add a bit to the end @@ -2626,39 +2691,37 @@ int test_bbackupd() TEST_THAT(f != 0); ::fprintf(f, "EXTRA STUFF"); ::fclose(f); - TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") + TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") > 1024); } // wait long enough for new files to be old enough to backup wait_for_operation(5, "new files to be old enough"); - // wait for backup daemon to do it's stuff - sync_and_wait(); + bbackupd.RunSyncNow(); // compare to make sure that it worked TEST_COMPARE(Compare_Same); // Try a quick compare, just for fun - compareReturnValue = ::system(BBACKUPQUERY " " - "-c testfiles/bbackupd.conf " - "-l testfiles/query2q.log " - "-Wwarning \"compare -acqQ\" quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - // Check that store errors are reported neatly - printf("\n==== Create store error\n"); - TEST_THAT(system("rm -f testfiles/notifyran.backup-error.*") - == 0); + TEST_COMPARE(Compare_Same, "", "-acqQ"); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Check that store errors are reported neatly. This test uses an independent +// daemon to check the daemon's backup loop delay, so it's easier to debug +// with the command: ./t -VTttest -e test_store_error_reporting +// --bbackupd-args=-kTtbbackupd +bool test_store_error_reporting() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + wait_for_sync_end(); + // TODO FIXME dedent + { // Break the store. We need a write lock on the account // while we do this, otherwise housekeeping might be running // and might rewrite the info files when it finishes, @@ -2669,10 +2732,7 @@ int test_bbackupd() ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs)); TEST_EQUAL_LINE(0, errs.size(), "Loading configuration file " "reported errors: " << errs); - TEST_THAT(config.get() != 0); - // Initialise the raid file controller - RaidFileController &rcontroller(RaidFileController::GetController()); - rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str()); + TEST_THAT_OR(config.get(), return false); std::auto_ptr<BackupStoreAccountDatabase> db( BackupStoreAccountDatabase::Read( config->GetKeyValue("AccountDatabase"))); @@ -2709,18 +2769,8 @@ int test_bbackupd() // Now kill bbackupd and start one that's running in // snapshot mode, check that it automatically syncs after // an error, without waiting for another sync command. - terminate_bbackupd(bbackupd_pid); - std::string cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd-snapshot.conf"; - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - ::safe_sleep(1); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - + TEST_THAT(StopClient()); + TEST_THAT(StartClient("testfiles/bbackupd-snapshot.conf")); sync_and_wait(); // Check that the error was reported once more @@ -2755,10 +2805,12 @@ int test_bbackupd() TEST_THAT(close(fd1) == 0); } - // bbackupd should pause for about 90 seconds from - // store_fixed_time, so check that it hasn't run after - // 85 seconds after store_fixed_time - wait_for_operation(85 - time(NULL) + store_fixed_time, + // bbackupd should pause for BACKUP_ERROR_RETRY_SECONDS (plus + // a random delay of up to mUpdateStoreInterval/64 or 0.05 + // extra seconds) from store_fixed_time, so check that it + // hasn't run just before this time + wait_for_operation(BACKUP_ERROR_DELAY_SHORTENED + + (store_fixed_time - time(NULL)) - 1, "just before bbackupd recovers"); TEST_THAT(!TestFileExists("testfiles/" "notifyran.backup-start.wait-snapshot.1")); @@ -2766,8 +2818,8 @@ int test_bbackupd() // Should not have backed up, should still get errors TEST_COMPARE(Compare_Different); - // wait another 10 seconds, bbackup should have run - wait_for_operation(10, "bbackupd to recover"); + // wait another 2 seconds, bbackup should have run + wait_for_operation(2, "bbackupd to recover"); TEST_THAT(TestFileExists("testfiles/" "notifyran.backup-start.wait-snapshot.1")); @@ -2777,7 +2829,7 @@ int test_bbackupd() TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); // Stop the snapshot bbackupd - terminate_bbackupd(bbackupd_pid); + TEST_THAT(StopClient()); // Break the store again TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf", @@ -2796,18 +2848,8 @@ int test_bbackupd() TEST_THAT(close(fd1) == 0); } - // Restart the old bbackupd, in automatic mode - cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd.conf"; - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); - ::safe_sleep(1); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - + // Restart bbackupd in automatic mode + TEST_THAT_OR(StartClient(), FAIL); sync_and_wait(); // Fix the store again @@ -2838,10 +2880,12 @@ int test_bbackupd() TEST_THAT(close(fd1) == 0); } - // bbackupd should pause for about 90 seconds from - // store_fixed_time, so check that it hasn't run after - // 85 seconds from store_fixed_time - wait_for_operation(85 - time(NULL) + store_fixed_time, + // bbackupd should pause for BACKUP_ERROR_RETRY_SECONDS (plus + // a random delay of up to mUpdateStoreInterval/64 or 0.05 + // extra seconds) from store_fixed_time, so check that it + // hasn't run just before this time + wait_for_operation(BACKUP_ERROR_DELAY_SHORTENED + + (store_fixed_time - time(NULL)) - 1, "just before bbackupd recovers"); TEST_THAT(!TestFileExists("testfiles/" "notifyran.backup-start.wait-automatic.1")); @@ -2849,8 +2893,8 @@ int test_bbackupd() // Should not have backed up, should still get errors TEST_COMPARE(Compare_Different); - // wait another 10 seconds, bbackup should have run - wait_for_operation(10, "bbackupd to recover"); + // wait another 2 seconds, bbackup should have run + wait_for_operation(2, "bbackupd to recover"); TEST_THAT(TestFileExists("testfiles/" "notifyran.backup-start.wait-automatic.1")); @@ -2858,28 +2902,39 @@ int test_bbackupd() TEST_COMPARE(Compare_Same); TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); + } + + TEARDOWN_TEST_BBACKUPD(); +} - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; +bool test_change_file_to_symlink_and_back() +{ + SETUP_WITH_BBSTORED(); + + #ifndef WIN32 + // New symlink + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/symlink-to-dir") == 0); + #endif - // Bad case: delete a file/symlink, replace it with a directory - printf("\n==== Replace symlink with directory, " - "add new directory\n"); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // Bad case: delete a file/symlink, replace it with a directory. + // Replace symlink with directory, add new directory. #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/symlink-to-dir") == 0); #endif - TEST_THAT(::mkdir("testfiles/TestDir1/symlink-to-dir", 0755) + TEST_THAT(::mkdir("testfiles/TestDir1/symlink-to-dir", 0755) == 0); - TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) + TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) == 0); - // NOTE: create a file within the directory to + // NOTE: create a file within the directory to // avoid deletion by the housekeeping process later #ifndef WIN32 @@ -2888,17 +2943,11 @@ int test_bbackupd() == 0); #endif - wait_for_backup_operation("bbackupd to sync the changes"); + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - // And the inverse, replace a directory with a file/symlink - printf("\n==== Replace directory with symlink\n"); #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" @@ -2912,19 +2961,12 @@ int test_bbackupd() "testfiles/TestDir1/x1/dir-to-file") == 0); #endif - wait_for_backup_operation("bbackupd to sync the changes"); - + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - // And then, put it back to how it was before. - printf("\n==== Replace symlink with directory " - "(which was a symlink)\n"); + BOX_INFO("Replace symlink with directory (which was a symlink)"); #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/x1" @@ -2940,21 +2982,14 @@ int test_bbackupd() == 0); #endif - wait_for_backup_operation("bbackupd to sync the changes"); - + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; // And finally, put it back to how it was before // it was put back to how it was before // This gets lots of nasty things in the store with // directories over other old directories. - printf("\n==== Put it all back to how it was\n"); #ifndef WIN32 TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" @@ -2968,19 +3003,23 @@ int test_bbackupd() "testfiles/TestDir1/x1/dir-to-file") == 0); #endif - wait_for_backup_operation("bbackupd to sync the changes"); - + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; +bool test_file_rename_tracking() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); - // rename an untracked file over an - // existing untracked file - printf("\n==== Rename over existing untracked file\n"); + // TODO FIXME dedent + { + // rename an untracked file over an existing untracked file + BOX_INFO("Rename over existing untracked file"); int fd1 = open("testfiles/TestDir1/untracked-1", O_CREAT | O_EXCL | O_WRONLY, 0700); int fd2 = open("testfiles/TestDir1/untracked-2", @@ -2997,9 +3036,7 @@ int test_bbackupd() // back up both files wait_for_operation(5, "untracked files to be old enough"); - wait_for_backup_operation("bbackupd to sync the " - "untracked files"); - + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); #ifdef WIN32 @@ -3012,21 +3049,13 @@ int test_bbackupd() TEST_THAT(!TestFileExists("testfiles/TestDir1/untracked-1")); TEST_THAT( TestFileExists("testfiles/TestDir1/untracked-2")); - wait_for_backup_operation("bbackupd to sync the untracked " - "files again"); - + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - // case which went wrong: rename a tracked file over an // existing tracked file - printf("\n==== Rename over existing tracked file\n"); - fd1 = open("testfiles/TestDir1/tracked-1", + BOX_INFO("Rename over existing tracked file"); + fd1 = open("testfiles/TestDir1/tracked-1", O_CREAT | O_EXCL | O_WRONLY, 0700); fd2 = open("testfiles/TestDir1/tracked-2", O_CREAT | O_EXCL | O_WRONLY, 0700); @@ -3045,11 +3074,7 @@ int test_bbackupd() // wait for them to be old enough to back up wait_for_operation(5, "tracked files to be old enough"); - - // back up both files - sync_and_wait(); - - // compare to make sure that it worked + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); #ifdef WIN32 @@ -3057,91 +3082,71 @@ int test_bbackupd() == 0); #endif - TEST_THAT(::rename("testfiles/TestDir1/tracked-1", + TEST_THAT(::rename("testfiles/TestDir1/tracked-1", "testfiles/TestDir1/tracked-2") == 0); TEST_THAT(!TestFileExists("testfiles/TestDir1/tracked-1")); TEST_THAT( TestFileExists("testfiles/TestDir1/tracked-2")); - wait_for_backup_operation("bbackupd to sync the tracked " - "files again"); - + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - // case which went wrong: rename a tracked file + // case which went wrong: rename a tracked file // over a deleted file - printf("\n==== Rename an existing file over a deleted file\n"); - TEST_THAT(!TestFileExists("testfiles/TestDir1/x1/dsfdsfs98.fd")); - TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", + BOX_INFO("Rename an existing file over a deleted file"); + TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", "testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); - - wait_for_backup_operation("bbackupd to sync"); + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - + // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} - printf("\n==== Add files with old times, update " - "attributes of one to latest time\n"); +// Files that suddenly appear, with timestamps before the last sync window, +// and files whose size or timestamp change, should still be uploaded, even +// though they look old. +bool test_upload_very_old_files() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); - // Move that file back - TEST_THAT(::rename("testfiles/TestDir1/x1/dsfdsfs98.fd", - "testfiles/TestDir1/df9834.dsf") == 0); - + // TODO FIXME dedent + { // Add some more files - // Because the 'm' option is not used, these files will + // Because the 'm' option is not used, these files will // look very old to the daemon. // Lucky it'll upload them then! - #ifdef WIN32 - TEST_THAT(::system("tar xzvf testfiles/test2.tgz " - "-C testfiles") == 0); - #else - TEST_THAT(::system("gzip -d < testfiles/test2.tgz " - "| ( cd testfiles && tar xf - )") == 0); + TEST_THAT(unpack_files("test2")); + + #ifndef WIN32 ::chmod("testfiles/TestDir1/sub23/dhsfdss/blf.h", 0415); #endif // Wait and test - wait_for_backup_operation("bbackupd to sync old files"); - + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); // Check that modifying files with old timestamps // still get added - printf("\n==== Modify existing file, but change timestamp " - "to rather old\n"); - wait_for_sync_end(); + BOX_INFO("Modify existing file, but change timestamp to rather old"); // Then modify an existing file { // in the archive, it's read only #ifdef WIN32 - TEST_THAT(::system("chmod 0777 testfiles" - "/TestDir1/sub23/rand.h") == 0); + TEST_THAT(::system("attrib -r " + "testfiles\\TestDir\\sub23\\rand.h") == 0); #else - TEST_THAT(chmod("testfiles/TestDir1/sub23" - "/rand.h", 0777) == 0); + TEST_THAT(chmod("testfiles/TestDir1/sub23/rand.h", + 0777) == 0); #endif FILE *f = fopen("testfiles/TestDir1/sub23/rand.h", @@ -3170,69 +3175,60 @@ int test_bbackupd() } // Wait and test - wait_for_sync_end(); // files too new - wait_for_sync_end(); // should (not) be backed up this time - - TEST_COMPARE(Compare_Same); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); // files too new? // Check that no read error has been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_excluded_files_are_not_backed_up() +{ + // SETUP_WITH_BBSTORED(); + SETUP_TEST_BBACKUPD(); + + BackupProtocolLocal2 client(0x01234567, "test", "backup/01234567/", + 0, false); + MockBackupDaemon bbackupd(client); + + TEST_THAT_OR(setup_test_bbackupd(bbackupd, + true, // do_unpack_files + false // do_start_bbstored + ), FAIL); + // TODO FIXME dedent + { // Add some files and directories which are marked as excluded - printf("\n==== Add files and dirs for exclusion test\n"); - #ifdef WIN32 - TEST_THAT(::system("tar xzvf testfiles/testexclude.tgz " - "-C testfiles") == 0); - #else - TEST_THAT(::system("gzip -d < " - "testfiles/testexclude.tgz " - "| ( cd testfiles && tar xf - )") == 0); - #endif + TEST_THAT(unpack_files("testexclude")); + bbackupd.RunSyncNow(); - // Wait and test - wait_for_sync_end(); - wait_for_sync_end(); - // compare with exclusions, should not find differences - TEST_COMPARE(Compare_Same); + // TEST_COMPARE(Compare_Same); + TEST_COMPARE_LOCAL(Compare_Same, client); // compare without exclusions, should find differences - compareReturnValue = ::system(BBACKUPQUERY " " - "-c testfiles/bbackupd.conf " - "-l testfiles/query3n.log " - "-Werror \"compare -acEQ\" quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Different); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + // TEST_COMPARE(Compare_Different, "", "-acEQ"); + TEST_COMPARE_LOCAL(Compare_Different, client, "acEQ"); // check that the excluded files did not make it // into the store, and the included files did - printf("\n==== Check that exclude/alwaysinclude commands " - "actually work\n"); - { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, + /* + std::auto_ptr<BackupProtocolCallable> pClient = + connect_and_login(context, BackupProtocolLogin::Flags_ReadOnly); + */ + BackupProtocolCallable* pClient = &client; - std::auto_ptr<BackupStoreDirectory> dir = - ReadDirectory(*client); + std::auto_ptr<BackupStoreDirectory> dir = + ReadDirectory(*pClient); int64_t testDirId = SearchDir(*dir, "Test1"); TEST_THAT(testDirId != 0); - dir = ReadDirectory(*client, testDirId); + dir = ReadDirectory(*pClient, testDirId); TEST_THAT(!SearchDir(*dir, "excluded_1")); TEST_THAT(!SearchDir(*dir, "excluded_2")); @@ -3247,32 +3243,44 @@ int test_bbackupd() int64_t sub23id = SearchDir(*dir, "sub23"); TEST_THAT(sub23id != 0); - dir = ReadDirectory(*client, sub23id); + dir = ReadDirectory(*pClient, sub23id); TEST_THAT(!SearchDir(*dir, "xx_not_this_dir_22")); TEST_THAT(!SearchDir(*dir, "somefile.excludethis")); - client->QueryFinished(); - sSocket.Close(); + + // client->QueryFinished(); } + } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} -#ifndef WIN32 +bool test_read_error_reporting() +{ + SETUP_WITH_BBSTORED(); + +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); +#else + if(::getuid() == 0) + { + BOX_NOTICE("skipping test because we're running as root"); // These tests only work as non-root users. - if(::getuid() != 0) + } + else + { + // TODO FIXME detent { // Check that the error has not been reported yet TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); // Check that read errors are reported neatly - printf("\n==== Add unreadable files\n"); - + BOX_INFO("Add unreadable files"); + { // Dir and file which can't be read + TEST_THAT(::mkdir("testfiles/TestDir1/sub23", + 0755) == 0); TEST_THAT(::mkdir("testfiles/TestDir1/sub23" "/read-fail-test-dir", 0000) == 0); int fd = ::open("testfiles/TestDir1" @@ -3282,9 +3290,8 @@ int test_bbackupd() ::close(fd); } - // Wait and test... - wait_for_backup_operation("bbackupd to try to sync " - "unreadable file"); + // Wait and test... with sysadmin notification + bbackupd.RunSyncNowWithExceptionHandling(); // should fail with an error due to unreadable file TEST_COMPARE(Compare_Error); @@ -3302,17 +3309,19 @@ int test_bbackupd() TEST_THAT(::chmod("testfiles/TestDir1" "/read-fail-test-file", 0770) == 0); } + } #endif - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} - printf("\n==== Continuously update file, " - "check isn't uploaded\n"); - +bool test_continuously_updated_file() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + + // TODO FIXME dedent + { // Make sure everything happens at the same point in the // sync cycle: wait until exactly the start of a sync wait_for_sync_start(); @@ -3321,7 +3330,8 @@ int test_bbackupd() ::safe_sleep(1); { - // Open a file, then save something to it every second + BOX_INFO("Open a file, then save something to it " + "every second for 12 seconds"); for(int l = 0; l < 12; ++l) { FILE *f = ::fopen("testfiles/TestDir1/continousupdate", "w+"); @@ -3329,23 +3339,18 @@ int test_bbackupd() fprintf(f, "Loop iteration %d\n", l); fflush(f); fclose(f); - - printf("."); - fflush(stdout); safe_sleep(1); } - printf("\n"); - fflush(stdout); // Check there's a difference - compareReturnValue = ::system("perl testfiles/" + int compareReturnValue = ::system("perl testfiles/" "extcheck1.pl"); TEST_RETURN(compareReturnValue, 1); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - printf("\n==== Keep on continuously updating file, " - "check it is uploaded eventually\n"); + BOX_INFO("Keep on continuously updating file for " + "28 seconds, check it is uploaded eventually"); for(int l = 0; l < 28; ++l) { @@ -3355,13 +3360,8 @@ int test_bbackupd() fprintf(f, "Loop 2 iteration %d\n", l); fflush(f); fclose(f); - - printf("."); - fflush(stdout); safe_sleep(1); } - printf("\n"); - fflush(stdout); compareReturnValue = ::system("perl testfiles/" "extcheck2.pl"); @@ -3369,42 +3369,62 @@ int test_bbackupd() TEST_RETURN(compareReturnValue, 1); TestRemoteProcessMemLeaks("bbackupquery.memleaks"); } - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + } - printf("\n==== Delete directory, change attributes\n"); - + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_delete_dir_change_attribute() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { // Delete a directory - TEST_THAT(::system("rm -rf testfiles/TestDir1/x1") == 0); - // Change attributes on an original file. - ::chmod("testfiles/TestDir1/df9834.dsf", 0423); - - // Wait and test - wait_for_backup_operation("bbackupd to sync deletion " - "of directory"); +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\x1") == 0); +#else + TEST_THAT(::system("rm -r testfiles/TestDir1/x1") == 0); +#endif + // Change attributes on an existing file. +#ifdef WIN32 + TEST_EQUAL(0, system("attrib +r testfiles\\TestDir1\\df9834.dsf")); +#else + TEST_THAT(::chmod("testfiles/TestDir1/df9834.dsf", 0423) == 0); +#endif + TEST_COMPARE(Compare_Different); + + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - - printf("\n==== Restore files and directories\n"); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_restore_files_and_directories() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { int64_t deldirid = 0; int64_t restoredirid = 0; { // connect and log in - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, BackupProtocolLogin::Flags_ReadOnly); // Find the ID of the Test1 directory - restoredirid = GetDirID(*client, "Test1", + restoredirid = GetDirID(*client, "Test1", BackupProtocolListDirectory::RootDirectory); - TEST_THAT(restoredirid != 0); + TEST_THAT_OR(restoredirid != 0, FAIL); // Test the restoration - TEST_THAT(BackupClientRestore(*client, restoredirid, + TEST_THAT(BackupClientRestore(*client, restoredirid, "Test1" /* remote */, "testfiles/restore-Test1" /* local */, true /* print progress dots */, @@ -3418,24 +3438,24 @@ int test_bbackupd() // to the server, so we'll compare later. // Make sure you can't restore a restored directory - TEST_THAT(BackupClientRestore(*client, restoredirid, - "Test1", "testfiles/restore-Test1", + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-Test1", true /* print progress dots */, false /* restore deleted */, false /* undelete after */, false /* resume */, false /* keep going */) == Restore_TargetExists); - + // Find ID of the deleted directory deldirid = GetDirID(*client, "x1", restoredirid); TEST_THAT(deldirid != 0); - // Just check it doesn't bomb out -- will check this + // Just check it doesn't bomb out -- will check this // properly later (when bbackupd is stopped) - TEST_THAT(BackupClientRestore(*client, deldirid, + TEST_THAT(BackupClientRestore(*client, deldirid, "Test1", "testfiles/restore-Test1-x1", - true /* print progress dots */, + true /* print progress dots */, true /* restore deleted */, false /* undelete after */, false /* resume */, @@ -3448,7 +3468,8 @@ int test_bbackupd() fflush(stdout); { - Logging::Guard guard(Log::FATAL); + Logger::LevelGuard(Logging::GetConsole(), + Log::FATAL); TEST_THAT(BackupClientRestore(*client, restoredirid, "Test1", "testfiles/no-such-path/subdir", @@ -3462,35 +3483,44 @@ int test_bbackupd() // Log out client->QueryFinished(); - sSocket.Close(); } // Compare the restored files TEST_COMPARE(Compare_Same); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + } -#ifdef WIN32 + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_compare_detects_attribute_changes() +{ + SETUP_WITH_BBSTORED(); + +#ifndef WIN32 + BOX_NOTICE("skipping test on this platform"); + // requires openfile(), GetFileTime() and attrib.exe +#else + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // TODO FIXME dedent + { // make one of the files read-only, expect a compare failure - compareReturnValue = ::system("attrib +r " - "testfiles\\restore-Test1\\f1.dat"); - TEST_RETURN(compareReturnValue, 0); + int exit_status = ::system("attrib +r " + "testfiles\\TestDir1\\f1.dat"); + TEST_RETURN(exit_status, 0); TEST_COMPARE(Compare_Different); // set it back, expect no failures - compareReturnValue = ::system("attrib -r " - "testfiles\\restore-Test1\\f1.dat"); - TEST_RETURN(compareReturnValue, 0); + exit_status = ::system("attrib -r " + "testfiles\\TestDir1\\f1.dat"); + TEST_RETURN(exit_status, 0); TEST_COMPARE(Compare_Same); // change the timestamp on a file, expect a compare failure - char* testfile = "testfiles\\restore-Test1\\f1.dat"; + const char* testfile = "testfiles\\TestDir1\\f1.dat"; HANDLE handle = openfile(testfile, O_RDWR, 0); TEST_THAT(handle != INVALID_HANDLE_VALUE); @@ -3524,90 +3554,95 @@ int test_bbackupd() TEST_THAT(set_file_time(testfile, creationTime, lastModTime, lastAccessTime)); TEST_COMPARE(Compare_Same); + } #endif // WIN32 - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} - printf("\n==== Add files with current time\n"); - - // Add some more files and modify others - // Use the m flag this time so they have a recent modification time - #ifdef WIN32 - TEST_THAT(::system("tar xzvmf testfiles/test3.tgz " - "-C testfiles") == 0); - #else - TEST_THAT(::system("gzip -d < testfiles/test3.tgz " - "| ( cd testfiles && tar xmf - )") == 0); - #endif +bool test_sync_new_files() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // Add some more files and modify others. Use the m flag this + // time so they have a recent modification time. + TEST_THAT(unpack_files("test3", "testfiles", "m")); + + // OpenBSD's tar interprets the "-m" option quite differently: + // it sets the time to epoch zero (1 Jan 1970) instead of the + // current time, which doesn't help us. So reset the timestamp + // on a file by touching it, so it won't be backed up. + { +#ifndef WIN32 + TEST_THAT(chmod("testfiles/TestDir1/chsh", 0755) == 0); +#endif + FileStream fs("testfiles/TestDir1/chsh", O_WRONLY); + fs.Write("a", 1); + } - // Wait and test - wait_for_backup_operation("bbackupd to sync new files"); + // At least one file is too new to be backed up on the first run. + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Different); + wait_for_operation(5, "newly added files to be old enough"); + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - // Rename directory - printf("\n==== Rename directory\n"); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_rename_operations() +{ + SETUP_WITH_BBSTORED(); + + TEST_THAT(unpack_files("test2")); + TEST_THAT(unpack_files("test3")); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // TODO FIXME dedent + { + BOX_INFO("Rename directory"); TEST_THAT(rename("testfiles/TestDir1/sub23/dhsfdss", "testfiles/TestDir1/renamed-dir") == 0); - wait_for_backup_operation("bbackupd to sync renamed directory"); - + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); // and again, but with quick flag - compareReturnValue = ::system(BBACKUPQUERY " " - "-c testfiles/bbackupd.conf " - "-l testfiles/query6q.log " - "-Wwarning \"compare -acqQ\" quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_COMPARE(Compare_Same, "", "-acqQ"); // Rename some files -- one under the threshold, others above - printf("\n==== Rename files\n"); - TEST_THAT(rename("testfiles/TestDir1/continousupdate", - "testfiles/TestDir1/continousupdate-ren") == 0); TEST_THAT(rename("testfiles/TestDir1/df324", "testfiles/TestDir1/df324-ren") == 0); TEST_THAT(rename("testfiles/TestDir1/sub23/find2perl", "testfiles/TestDir1/find2perl-ren") == 0); - wait_for_backup_operation("bbackupd to sync renamed files"); - + bbackupd.RunSyncNow(); TEST_COMPARE(Compare_Same); + } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - // Check that modifying files with madly in the future - // timestamps still get added - printf("\n==== Create a file with timestamp way ahead " - "in the future\n"); - - // Time critical, so sync - wait_for_sync_start(); + TEARDOWN_TEST_BBACKUPD(); +} - // Then wait a second, to make sure the scan is complete - ::safe_sleep(1); +// Check that modifying files with madly in the future timestamps still get added +bool test_sync_files_with_timestamps_in_future() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); - // Then modify an existing file + // TODO FIXME dedent + { { + TEST_THAT(::mkdir("testfiles/TestDir1/sub23", + 0755) == 0); FILE *f = fopen("testfiles/TestDir1/sub23/" "in-the-future", "w"); - TEST_THAT(f != 0); + TEST_THAT_OR(f != 0, FAIL); fprintf(f, "Back to the future!\n"); fclose(f); // and then move the time forwards! @@ -3621,17 +3656,41 @@ int test_bbackupd() } // Wait and test + bbackupd.RunSyncNow(); wait_for_backup_operation("bbackup to sync future file"); TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; +// Check change of store marker pauses daemon +bool test_changing_client_store_marker_pauses_daemon() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + + // Wait for the client to upload all current files. We also time + // approximately how long a sync takes. + box_time_t sync_start_time = GetCurrentBoxTime(); + sync_and_wait(); + box_time_t sync_time = GetCurrentBoxTime() - sync_start_time; + BOX_INFO("Sync takes " << BOX_FORMAT_MICROSECONDS(sync_time)); - printf("\n==== Change client store marker\n"); + // Time how long a compare takes. On NetBSD it's 3 seconds, and that + // interferes with test timing unless we account for it. + box_time_t compare_start_time = GetCurrentBoxTime(); + // There should be no differences right now (yet). + TEST_COMPARE(Compare_Same); + box_time_t compare_time = GetCurrentBoxTime() - compare_start_time; + BOX_INFO("Compare takes " << BOX_FORMAT_MICROSECONDS(compare_time)); + // Wait for the end of another sync, to give us ~3 seconds to change + // the client store marker. + wait_for_sync_end(); + + // TODO FIXME dedent + { // Then... connect to the server, and change the // client store marker. See what that does! { @@ -3641,8 +3700,8 @@ int test_bbackupd() { try { - std::auto_ptr<BackupProtocolClient> - protocol = Connect(context); + std::auto_ptr<BackupProtocolCallable> + protocol = connect_to_bbstored(sTlsContext); // Make sure the marker isn't zero, // because that's the default, and // it should have changed @@ -3650,6 +3709,9 @@ int test_bbackupd() TEST_THAT(loginConf->GetClientStoreMarker() != 0); // Change it to something else + BOX_INFO("Changing client store marker " + "from " << loginConf->GetClientStoreMarker() << + " to 12"); protocol->QuerySetClientStoreMarker(12); // Success! @@ -3657,7 +3719,13 @@ int test_bbackupd() // Log out protocol->QueryFinished(); - sSocket.Close(); + } + catch(BoxException &e) + { + BOX_INFO("Failed to connect to bbstored, " + << tries << " retries remaining: " + << e.what()); + tries--; } catch(...) { @@ -3666,15 +3734,7 @@ int test_bbackupd() } TEST_THAT(done); } - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - printf("\n==== Check change of store marker pauses daemon\n"); - // Make a change to a file, to detect whether or not // it's hanging around waiting to retry. { @@ -3684,51 +3744,80 @@ int test_bbackupd() ::fclose(f); } - // Wait a little bit longer than usual - wait_for_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION * - 3) / 2, "bbackupd to detect changed store marker"); + // Wait for bbackupd to detect the problem. + wait_for_sync_end(); - // Test that there *are* differences + // Test that there *are* differences still, i.e. that bbackupd + // didn't successfully run a backup during that time. + BOX_INFO("Compare starting, expecting differences"); TEST_COMPARE(Compare_Different); - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + BOX_TRACE("Compare finished, expected differences"); + + // Wait out the expected delay in bbackupd. This is quite + // time-sensitive, so we use sub-second precision. + box_time_t wait = + SecondsToBoxTime(BACKUP_ERROR_DELAY_SHORTENED - 1) - + compare_time * 2; + BOX_INFO("Waiting for " << BOX_FORMAT_MICROSECONDS(wait) << " " + "until just before bbackupd recovers"); + ShortSleep(wait, true); + + // bbackupd should not have recovered yet, so there should + // still be differences. + BOX_INFO("Compare starting, expecting differences"); + TEST_COMPARE(Compare_Different); + BOX_TRACE("Compare finished, expected differences"); + + // Now wait for it to recover and finish a sync, and check that + // the differences are gone (successful backup). Wait until ~2 + // seconds after we expect the sync to have finished, to reduce + // the risk of random failure on AppVeyor when heavily loaded. + wait = sync_time + SecondsToBoxTime(6); + BOX_INFO("Waiting for " << BOX_FORMAT_MICROSECONDS(wait) << + " until just after bbackupd recovers and finishes a sync"); + ShortSleep(wait, true); + + BOX_INFO("Compare starting, expecting no differences"); + TEST_COMPARE(Compare_Same); + BOX_TRACE("Compare finished, expected no differences"); + } - wait_for_operation(100, "bbackupd to recover"); + TEARDOWN_TEST_BBACKUPD(); +} - // Then check it has backed up successfully. - TEST_COMPARE(Compare_Same); +bool test_interrupted_restore_can_be_recovered() +{ + SETUP_WITH_BBSTORED(); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); +#else + bbackupd.RunSyncNow(); -#ifndef WIN32 - printf("\n==== Interrupted restore\n"); + // TODO FIXME dedent + { { - do_interrupted_restore(context, restoredirid); + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, + BackupProtocolLogin::Flags_ReadOnly); + + // Find the ID of the Test1 directory + int64_t restoredirid = GetDirID(*client, "Test1", + BackupProtocolListDirectory::RootDirectory); + TEST_THAT_OR(restoredirid != 0, FAIL); + + do_interrupted_restore(sTlsContext, restoredirid); int64_t resumesize = 0; TEST_THAT(FileExists("testfiles/" - "restore-interrupt.boxbackupresume", + "restore-interrupt.boxbackupresume", &resumesize)); // make sure it has recorded something to resume - TEST_THAT(resumesize > 16); - - printf("\n==== Resume restore\n"); - - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, - BackupProtocolLogin::Flags_ReadOnly); + TEST_THAT(resumesize > 16); // Check that the restore fn returns resume possible, // rather than doing anything - TEST_THAT(BackupClientRestore(*client, restoredirid, - "Test1", "testfiles/restore-interrupt", + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-interrupt", true /* print progress dots */, false /* restore deleted */, false /* undelete after */, @@ -3737,8 +3826,8 @@ int test_bbackupd() == Restore_ResumePossible); // Then resume it - TEST_THAT(BackupClientRestore(*client, restoredirid, - "Test1", "testfiles/restore-interrupt", + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-interrupt", true /* print progress dots */, false /* restore deleted */, false /* undelete after */, @@ -3747,28 +3836,73 @@ int test_bbackupd() == Restore_Complete); client->QueryFinished(); - sSocket.Close(); + client.reset(); // Then check it has restored the correct stuff TEST_COMPARE(Compare_Same); } + } #endif // !WIN32 - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} + +bool assert_x1_deleted_or_not(bool expected_deleted) +{ + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0 /* read-write */); + + std::auto_ptr<BackupStoreDirectory> dir = ReadDirectory(*client); + int64_t testDirId = SearchDir(*dir, "Test1"); + TEST_THAT_OR(testDirId != 0, return false); + + dir = ReadDirectory(*client, testDirId); + BackupStoreDirectory::Iterator i(*dir); + BackupStoreFilenameClear child("x1"); + BackupStoreDirectory::Entry *en = i.FindMatchingClearName(child); + TEST_THAT_OR(en != 0, return false); + TEST_EQUAL_OR(expected_deleted, en->IsDeleted(), return false); + + return true; +} + +bool test_restore_deleted_files() +{ + SETUP_WITH_BBSTORED(); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); - printf("\n==== Check restore deleted files\n"); + TEST_THAT(::unlink("testfiles/TestDir1/f1.dat") == 0); +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\x1") == 0); +#else + TEST_THAT(::system("rm -r testfiles/TestDir1/x1") == 0); +#endif + TEST_COMPARE(Compare_Different); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + TEST_THAT(assert_x1_deleted_or_not(true)); + + // TODO FIXME dedent + { { - std::auto_ptr<BackupProtocolClient> client = - ConnectAndLogin(context, 0 /* read-write */); + std::auto_ptr<BackupProtocolCallable> client = + connect_and_login(sTlsContext, 0 /* read-write */); + + // Find the ID of the Test1 directory + int64_t restoredirid = GetDirID(*client, "Test1", + BackupProtocolListDirectory::RootDirectory); + TEST_THAT_OR(restoredirid != 0, FAIL); + + // Find ID of the deleted directory + int64_t deldirid = GetDirID(*client, "x1", restoredirid); + TEST_THAT_OR(deldirid != 0, FAIL); // Do restore and undelete - TEST_THAT(BackupClientRestore(*client, deldirid, - "Test1", "testfiles/restore-Test1-x1-2", + TEST_THAT(BackupClientRestore(*client, deldirid, + "Test1", "testfiles/restore-Test1-x1-2", true /* print progress dots */, true /* deleted files */, true /* undelete after */, @@ -3777,181 +3911,118 @@ int test_bbackupd() == Restore_Complete); client->QueryFinished(); - sSocket.Close(); + client.reset(); // Do a compare with the now undeleted files - compareReturnValue = ::system(BBACKUPQUERY " " - "-c testfiles/bbackupd.conf " - "-l testfiles/query11.log " - "-Wwarning " - "\"compare -cEQ Test1/x1 " - "testfiles/restore-Test1-x1-2\" quit"); - TEST_RETURN(compareReturnValue, - BackupQueries::ReturnCode::Compare_Same); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/x1 " + "testfiles/restore-Test1-x1-2"); } // Final check on notifications TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); + } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + // should have been undeleted by restore + TEST_THAT(assert_x1_deleted_or_not(false)); -#ifdef WIN32 - printf("\n==== Testing locked file behaviour:\n"); + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_locked_file_behaviour() +{ + SETUP_WITH_BBSTORED(); +#ifndef WIN32 + // There are no tests for mandatory locks on non-Windows platforms yet. + BOX_NOTICE("skipping test on this platform"); +#else + // TODO FIXME dedent + { // Test that locked files cannot be backed up, // and the appropriate error is reported. - // Wait for the sync to finish, so that we have time to work - wait_for_sync_end(); - // Now we have about three seconds to work - handle = openfile("testfiles/TestDir1/lockedfile", - O_CREAT | O_EXCL | O_LOCK, 0); - TEST_THAT(handle != INVALID_HANDLE_VALUE); + HANDLE handle = openfile("testfiles/TestDir1/f1.dat", + BOX_OPEN_LOCK, 0); + TEST_THAT_OR(handle != INVALID_HANDLE_VALUE, FAIL); - if (handle != 0) - { - // first sync will ignore the file, it's too new - wait_for_sync_end(); - TEST_THAT(!TestFileExists("testfiles/" - "notifyran.read-error.1")); - } - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - if (handle != 0) { // this sync should try to back up the file, // and fail, because it's locked - wait_for_sync_end(); + bbackupd.RunSyncNowWithExceptionHandling(); TEST_THAT(TestFileExists("testfiles/" "notifyran.read-error.1")); TEST_THAT(!TestFileExists("testfiles/" "notifyran.read-error.2")); } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - if (handle != 0) { // now close the file and check that it is // backed up on the next run. CloseHandle(handle); - wait_for_sync_end(); + bbackupd.RunSyncNow(); // still no read errors? TEST_THAT(!TestFileExists("testfiles/" "notifyran.read-error.2")); + TEST_COMPARE(Compare_Same); } - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - if (handle != 0) - { - // compare, and check that it works - // reports the correct error message (and finishes) - TEST_THAT(compare_all(false)); - } - - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; - - if (handle != 0) { // open the file again, compare and check that compare // reports the correct error message (and finishes) - handle = openfile("testfiles/TestDir1/lockedfile", - O_LOCK, 0); - TEST_THAT(handle != INVALID_HANDLE_VALUE); + handle = openfile("testfiles/TestDir1/f1.dat", + BOX_OPEN_LOCK, 0); + TEST_THAT_OR(handle != INVALID_HANDLE_VALUE, FAIL); TEST_COMPARE(Compare_Error); // close the file again, check that compare // works again CloseHandle(handle); + TEST_COMPARE(Compare_Same); } + } +#endif // WIN32 - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} - if (handle != 0) - { - TEST_COMPARE(Compare_Same); - } -#endif +bool test_backup_many_files() +{ + SETUP_WITH_BBSTORED(); - // Kill the daemon - terminate_bbackupd(bbackupd_pid); - - // Start it again - cmd = BBACKUPD " " + bbackupd_args + - " testfiles/bbackupd.conf"; - bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + unpack_files("test2"); + unpack_files("test3"); + unpack_files("testexclude"); + unpack_files("spacetest1", "testfiles/TestDir1"); + unpack_files("spacetest2", "testfiles/TestDir1"); - TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); - TEST_THAT(ServerIsAlive(bbackupd_pid)); - TEST_THAT(ServerIsAlive(bbstored_pid)); - if (!ServerIsAlive(bbackupd_pid)) return 1; - if (!ServerIsAlive(bbstored_pid)) return 1; - if (failures) return 1; + TEARDOWN_TEST_BBACKUPD(); +} - if(bbackupd_pid != -1 && bbackupd_pid != 0) - { - // Wait and compare (a little bit longer than usual) - wait_for_operation( - (TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2, - "bbackupd to sync everything"); - TEST_COMPARE(Compare_Same); +bool test_parse_incomplete_command() +{ + SETUP_TEST_BBACKUPD(); - // Kill it again - terminate_bbackupd(bbackupd_pid); - } + { + // This is not a complete command, it should not parse! + BackupQueries::ParsedCommand cmd("-od", true); + TEST_THAT(cmd.mFailed); + TEST_EQUAL((void *)NULL, cmd.pSpec); + TEST_EQUAL(0, cmd.mCompleteArgCount); } - /* - // List the files on the server - why? - ::system(BBACKUPQUERY " -q -c testfiles/bbackupd.conf " - "-l testfiles/queryLIST.log \"list -rotdh\" quit"); - TestRemoteProcessMemLeaks("bbackupquery.memleaks"); - */ - - #ifndef WIN32 - if(::getuid() == 0) - { - ::printf("WARNING: This test was run as root. " - "Some tests have been omitted.\n"); - } - #endif - - return 0; + TEARDOWN_TEST_BBACKUPD(); } -int test(int argc, const char *argv[]) +bool test_parse_syncallowscript_output() { + SETUP_TEST_BBACKUPD(); + { BackupDaemon daemon; @@ -3965,48 +4036,83 @@ int test(int argc, const char *argv[]) TEST_EQUAL(0, daemon.GetMaxBandwidthFromSyncAllowScript()); } + TEARDOWN_TEST_BBACKUPD(); +} + +int test(int argc, const char *argv[]) +{ // SSL library SSLLib::Initialise(); // Keys for subsystems BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); - // Initial files - #ifdef WIN32 - TEST_THAT(::system("tar xzvf testfiles/test_base.tgz " - "-C testfiles") == 0); - #else - TEST_THAT(::system("gzip -d < testfiles/test_base.tgz " - "| ( cd testfiles && tar xf - )") == 0); - #endif + { + std::string errs; + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify + ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs)); + TEST_EQUAL_LINE(0, errs.size(), "Loading configuration file " + "reported errors: " << errs); + TEST_THAT_OR(config.get(), return 1); + // Initialise the raid file controller + RaidFileController &rcontroller(RaidFileController::GetController()); + rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str()); + } - // Do the tests + sTlsContext.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); - int r = test_basics(); - if(r != 0) return r; - - r = test_setupaccount(); - if(r != 0) return r; + TEST_THAT(test_basics()); + TEST_THAT(test_readdirectory_on_nonexistent_dir()); + TEST_THAT(test_bbackupquery_parser_escape_slashes()); + TEST_THAT(test_getobject_on_nonexistent_file()); + // TEST_THAT(test_replace_zero_byte_file_with_nonzero_byte_file()); + TEST_THAT(test_backup_disappearing_directory()); + TEST_THAT(test_ssl_keepalives()); + TEST_THAT(test_backup_hardlinked_files()); + TEST_THAT(test_backup_pauses_when_store_is_full()); + TEST_THAT(test_bbackupd_exclusions()); + TEST_THAT(test_bbackupd_uploads_files()); + TEST_THAT(test_bbackupd_responds_to_connection_failure()); + TEST_THAT(test_absolute_symlinks_not_followed_during_restore()); + TEST_THAT(test_initially_missing_locations_are_not_forgotten()); + TEST_THAT(test_redundant_locations_deleted_on_time()); + TEST_THAT(test_read_only_dirs_can_be_restored()); + TEST_THAT(test_unicode_filenames_can_be_backed_up()); + TEST_THAT(test_sync_allow_script_can_pause_backup()); + TEST_THAT(test_delete_update_and_symlink_files()); + TEST_THAT(test_store_error_reporting()); + TEST_THAT(test_change_file_to_symlink_and_back()); + TEST_THAT(test_file_rename_tracking()); + TEST_THAT(test_upload_very_old_files()); + TEST_THAT(test_excluded_files_are_not_backed_up()); + TEST_THAT(test_read_error_reporting()); + TEST_THAT(test_continuously_updated_file()); + TEST_THAT(test_delete_dir_change_attribute()); + TEST_THAT(test_restore_files_and_directories()); + TEST_THAT(test_compare_detects_attribute_changes()); + TEST_THAT(test_sync_new_files()); + TEST_THAT(test_rename_operations()); + TEST_THAT(test_sync_files_with_timestamps_in_future()); + TEST_THAT(test_changing_client_store_marker_pauses_daemon()); + TEST_THAT(test_interrupted_restore_can_be_recovered()); + TEST_THAT(test_restore_deleted_files()); + TEST_THAT(test_locked_file_behaviour()); + TEST_THAT(test_backup_many_files()); + TEST_THAT(test_parse_incomplete_command()); + TEST_THAT(test_parse_syncallowscript_output()); + + TEST_THAT(kill_running_daemons()); - r = test_run_bbstored(); - TEST_THAT(r == 0); - if(r != 0) return r; - - r = test_bbackupd(); - if(r != 0) +#ifndef WIN32 + if(::getuid() == 0) { - if (bbackupd_pid) - { - KillServer(bbackupd_pid); - } - if (bbstored_pid) - { - KillServer(bbstored_pid); - } - return r; + BOX_WARNING("This test was run as root. Some tests have been omitted."); } - - test_kill_bbstored(); +#endif - return 0; + return finish_test_suite(); } |