diff options
Diffstat (limited to 'lib/common/Test.h')
-rw-r--r-- | lib/common/Test.h | 434 |
1 files changed, 304 insertions, 130 deletions
diff --git a/lib/common/Test.h b/lib/common/Test.h index 0cdcabb2..fcac260e 100644 --- a/lib/common/Test.h +++ b/lib/common/Test.h @@ -1,4 +1,4 @@ -// distribution boxbackup-0.10 (svn version: 494) +// distribution boxbackup-0.11rc1 (svn version: 2023_2024) // // Copyright (c) 2003 - 2006 // Ben Summers and contributors. All rights reserved. @@ -48,26 +48,58 @@ #ifndef TEST__H #define TEST__H -#include <sys/types.h> -#include <sys/stat.h> -#include <stdlib.h> +#include <errno.h> #include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +#include <sys/stat.h> +#include <sys/types.h> #ifdef HAVE_UNISTD_H #include <unistd.h> #endif -#include <stdio.h> - +#include <string> + +#ifdef WIN32 +#define BBACKUPCTL "..\\..\\bin\\bbackupctl\\bbackupctl.exe" +#define BBACKUPD "..\\..\\bin\\bbackupd\\bbackupd.exe" +#define BBSTORED "..\\..\\bin\\bbstored\\bbstored.exe" +#define BBACKUPQUERY "..\\..\\bin\\bbackupquery\\bbackupquery.exe" +#define BBSTOREACCOUNTS "..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts.exe" +#define TEST_RETURN(actual, expected) TEST_THAT(actual == expected); +#else +#define BBACKUPCTL "../../bin/bbackupctl/bbackupctl" +#define BBACKUPD "../../bin/bbackupd/bbackupd" +#define BBSTORED "../../bin/bbstored/bbstored" +#define BBACKUPQUERY "../../bin/bbackupquery/bbackupquery" +#define BBSTOREACCOUNTS "../../bin/bbstoreaccounts/bbstoreaccounts" +#define TEST_RETURN(actual, expected) TEST_THAT(actual == expected*256); +#endif + extern int failures; +extern int first_fail_line; +extern std::string first_fail_file; +extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args; + +#define TEST_FAIL_WITH_MESSAGE(msg) \ +{ \ + if (failures == 0) \ + { \ + first_fail_file = __FILE__; \ + first_fail_line = __LINE__; \ + } \ + failures++; \ + printf("FAILURE: " msg " at " __FILE__ "(%d)\n", __LINE__); \ +} -#define TEST_FAIL_WITH_MESSAGE(msg) {failures++; printf("FAILURE: " msg " at " __FILE__ "(%d)\n", __LINE__);} -#define TEST_ABORT_WITH_MESSAGE(msg) {failures++; printf("FAILURE: " msg " at " __FILE__ "(%d)\n", __LINE__); return 1;} +#define TEST_ABORT_WITH_MESSAGE(msg) {TEST_FAIL_WITH_MESSAGE(msg); return 1;} #define TEST_THAT(condition) {if(!(condition)) TEST_FAIL_WITH_MESSAGE("Condition [" #condition "] failed")} #define TEST_THAT_ABORTONFAIL(condition) {if(!(condition)) TEST_ABORT_WITH_MESSAGE("Condition [" #condition "] failed")} -// NOTE: The 0- bit it to allow this to work with stuff which has negative constants for flags (eg ConnectionException) +// NOTE: The 0- bit is to allow this to work with stuff which has negative constants for flags (eg ConnectionException) #define TEST_CHECK_THROWS(statement, excepttype, subtype) \ { \ bool didthrow = false; \ @@ -117,30 +149,74 @@ inline int TestGetFileSize(const char *Filename) return -1; } -inline int LaunchServer(const char *CommandLine, const char *pidFile) +inline std::string ConvertPaths(const std::string& rOriginal) { - if(::system(CommandLine) != 0) +#ifdef WIN32 + // convert UNIX paths to native + + std::string converted; + for (size_t i = 0; i < rOriginal.size(); i++) { - printf("Server: %s\n", CommandLine); - TEST_FAIL_WITH_MESSAGE("Couldn't start server"); - return -1; + if (rOriginal[i] == '/') + { + converted += '\\'; + } + else + { + converted += rOriginal[i]; + } } - // time for it to start up - ::sleep(1); - - // read pid file + return converted; + +#else // !WIN32 + return rOriginal; +#endif +} + +inline int RunCommand(const std::string& rCommandLine) +{ + return ::system(ConvertPaths(rCommandLine).c_str()); +} + +#ifdef WIN32 +#include <windows.h> +#endif + +inline bool ServerIsAlive(int pid) +{ +#ifdef WIN32 + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid); + if (hProcess == NULL) + { + if (GetLastError() != ERROR_INVALID_PARAMETER) + { + printf("Failed to open process %d: error %d\n", + pid, (int)GetLastError()); + } + return false; + } + CloseHandle(hProcess); + return true; +#else // !WIN32 + if(pid == 0) return false; + return ::kill(pid, 0) != -1; +#endif // WIN32 +} + +inline int ReadPidFile(const char *pidFile) +{ if(!TestFileExists(pidFile)) { - printf("Server: %s\n", CommandLine); - TEST_FAIL_WITH_MESSAGE("Server didn't save PID file"); + TEST_FAIL_WITH_MESSAGE("Server didn't save PID file " + "(perhaps one was already running?)"); return -1; } - FILE *f = fopen(pidFile, "r"); int pid = -1; + + FILE *f = fopen(pidFile, "r"); if(f == NULL || fscanf(f, "%d", &pid) != 1) { - printf("Server: %s (pidfile %s)\n", CommandLine, pidFile); TEST_FAIL_WITH_MESSAGE("Couldn't read PID file"); return -1; } @@ -149,160 +225,177 @@ inline int LaunchServer(const char *CommandLine, const char *pidFile) return pid; } -#ifdef WIN32 - -#include "WinNamedPipeStream.h" -#include "IOStreamGetLine.h" -#include "BoxPortsAndFiles.h" - -bool SendCommands(const std::string& rCmd) +inline int LaunchServer(const std::string& rCommandLine, const char *pidFile) { - WinNamedPipeStream connection; +#ifdef WIN32 - try + PROCESS_INFORMATION procInfo; + + STARTUPINFO startInfo; + startInfo.cb = sizeof(startInfo); + startInfo.lpReserved = NULL; + startInfo.lpDesktop = NULL; + startInfo.lpTitle = NULL; + startInfo.dwFlags = 0; + startInfo.cbReserved2 = 0; + startInfo.lpReserved2 = NULL; + + std::string cmd = ConvertPaths(rCommandLine); + CHAR* tempCmd = strdup(cmd.c_str()); + + DWORD result = CreateProcess + ( + NULL, // lpApplicationName, naughty! + tempCmd, // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + false, // bInheritHandles + 0, // dwCreationFlags + NULL, // lpEnvironment + NULL, // lpCurrentDirectory + &startInfo, // lpStartupInfo + &procInfo // lpProcessInformation + ); + + free(tempCmd); + + if (result == 0) { - connection.Connect(BOX_NAMED_PIPE_NAME); - } - catch(...) - { - printf("Failed to connect to daemon control socket.\n"); - return false; + DWORD err = GetLastError(); + printf("Launch failed: %s: error %d\n", rCommandLine.c_str(), + (int)err); + return -1; } - // For receiving data - IOStreamGetLine getLine(connection); - - // Wait for the configuration summary - std::string configSummary; - if(!getLine.GetLine(configSummary)) - { - printf("Failed to receive configuration summary from daemon\n"); - return false; - } + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); - // Was the connection rejected by the server? - if(getLine.IsEOF()) - { - printf("Server rejected the connection.\n"); - return false; - } +#else // !WIN32 - // Decode it - int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; - if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", - &autoBackup, &updateStoreInterval, - &minimumFileAge, &maxUploadWait) != 4) + if(RunCommand(rCommandLine) != 0) { - printf("Config summary didn't decode\n"); - return false; + printf("Server: %s\n", rCommandLine.c_str()); + TEST_FAIL_WITH_MESSAGE("Couldn't start server"); + return -1; } - std::string cmds; - bool expectResponse; +#endif // WIN32 - if (rCmd != "") - { - cmds = rCmd; - cmds += "\nquit\n"; - expectResponse = true; - } - else + #ifdef WIN32 + // on other platforms there is no other way to get + // the PID, so a NULL pidFile doesn't make sense. + + if (pidFile == NULL) { - cmds = "quit\n"; - expectResponse = false; + return (int)procInfo.dwProcessId; } - - connection.Write(cmds.c_str(), cmds.size()); - - // Read the response - std::string line; - bool statusOk = !expectResponse; + #endif + + // time for it to start up + ::fprintf(stdout, "Starting server: %s\n", rCommandLine.c_str()); + ::fprintf(stdout, "Waiting for server to start: "); - while (expectResponse && !getLine.IsEOF() && getLine.GetLine(line)) + for (int i = 0; i < 15; i++) { - // Is this an OK or error line? - if (line == "ok") - { - statusOk = true; - } - else if (line == "error") + if (TestFileExists(pidFile)) { - printf("ERROR (%s)\n", rCmd.c_str()); break; } - else + + #ifdef WIN32 + if (!ServerIsAlive((int)procInfo.dwProcessId)) { - printf("WARNING: Unexpected response to command '%s': " - "%s", rCmd.c_str(), line.c_str()); + break; } + #endif + + ::fprintf(stdout, "."); + ::fflush(stdout); + ::sleep(1); } - - return statusOk; -} -inline bool ServerIsAlive() -{ - return SendCommands(""); -} + #ifdef WIN32 + // on Win32 we can check whether the process is alive + // without even checking the PID file -inline bool HUPServer(int pid) -{ - return SendCommands("reload"); -} + if (!ServerIsAlive((int)procInfo.dwProcessId)) + { + ::fprintf(stdout, "server died!\n"); + TEST_FAIL_WITH_MESSAGE("Server died!"); + return -1; + } + #endif -inline bool KillServer(int pid) -{ - TEST_THAT(SendCommands("terminate")); + if (!TestFileExists(pidFile)) + { + ::fprintf(stdout, "timed out!\n"); + TEST_FAIL_WITH_MESSAGE("Server didn't save PID file"); + return -1; + } + + ::fprintf(stdout, "done.\n"); + + // wait a second for the pid to be written to the file ::sleep(1); - return !ServerIsAlive(); -} -#else // !WIN32 + // read pid file + int pid = ReadPidFile(pidFile); -inline bool ServerIsAlive(int pid) -{ - if(pid == 0) return false; - return ::kill(pid, 0) != -1; -} + #ifdef WIN32 + // On Win32 we can check whether the PID in the pidFile matches + // the one returned by the system, which it always should. -inline bool HUPServer(int pid) -{ - if(pid == 0) return false; - return ::kill(pid, SIGHUP) != -1; -} + if (pid != (int)procInfo.dwProcessId) + { + printf("Server wrote wrong pid to file (%s): expected %d " + "but found %d\n", pidFile, + (int)procInfo.dwProcessId, pid); + TEST_FAIL_WITH_MESSAGE("Server wrote wrong pid to file"); + return -1; + } + #endif -inline bool KillServer(int pid) -{ - if(pid == 0 || pid == -1) return false; - bool KilledOK = ::kill(pid, SIGTERM) != -1; - TEST_THAT(KilledOK); - ::sleep(1); - return !ServerIsAlive(pid); + return pid; } -#endif // WIN32 +#define TestRemoteProcessMemLeaks(filename) \ + TestRemoteProcessMemLeaksFunc(filename, __FILE__, __LINE__) -inline void TestRemoteProcessMemLeaks(const char *filename) +inline void TestRemoteProcessMemLeaksFunc(const char *filename, + const char* file, int line) { #ifdef BOX_MEMORY_LEAK_TESTING // Does the file exist? if(!TestFileExists(filename)) { + if (failures == 0) + { + first_fail_file = file; + first_fail_line = line; + } ++failures; - printf("FAILURE: MemLeak report not available (file %s)\n", filename); + printf("FAILURE: MemLeak report not available (file %s) " + "at %s:%d\n", filename, file, line); } else { // Is it empty? if(TestGetFileSize(filename) > 0) { + if (failures == 0) + { + first_fail_file = file; + first_fail_line = line; + } ++failures; - printf("FAILURE: Memory leaks found in other process (file %s)\n==========\n", filename); + printf("FAILURE: Memory leaks found in other process " + "(file %s) at %s:%d\n==========\n", + filename, file, line); FILE *f = fopen(filename, "r"); - char line[512]; - while(::fgets(line, sizeof(line), f) != 0) + char linebuf[512]; + while(::fgets(linebuf, sizeof(linebuf), f) != 0) { - printf("%s", line); + printf("%s", linebuf); } fclose(f); printf("==========\n"); @@ -314,5 +407,86 @@ inline void TestRemoteProcessMemLeaks(const char *filename) #endif } -#endif // TEST__H +inline void force_sync() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "force-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +inline void wait_for_sync_start() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "wait-for-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +inline void wait_for_sync_end() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "wait-for-end") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +inline void sync_and_wait() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "sync-and-wait") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +inline void terminate_bbackupd(int pid) +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "terminate") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + + for (int i = 0; i < 20; i++) + { + if (!ServerIsAlive(pid)) break; + fprintf(stdout, "."); + fflush(stdout); + sleep(1); + } + + TEST_THAT(!ServerIsAlive(pid)); + TestRemoteProcessMemLeaks("bbackupd.memleaks"); +} + + +// Wait a given number of seconds for something to complete +inline void wait_for_operation(int seconds) +{ + printf("Waiting: "); + fflush(stdout); + for(int l = 0; l < seconds; ++l) + { + sleep(1); + printf("."); + fflush(stdout); + } + printf(" done.\n"); + fflush(stdout); +} + +inline void safe_sleep(int seconds) +{ +#ifdef WIN32 + Sleep(seconds * 1000); +#else + struct timespec ts; + memset(&ts, 0, sizeof(ts)); + ts.tv_sec = seconds; + ts.tv_nsec = 0; + BOX_TRACE("sleeping for " << seconds << " seconds"); + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) + { + BOX_TRACE("safe_sleep interrupted with " << + ts.tv_sec << "." << ts.tv_nsec << + " secs remaining, sleeping again"); + /* sleep again */ + } +#endif +} +#endif // TEST__H |