From b8d3d77eedcd9aa052dd0cdb68ac9508613150b4 Mon Sep 17 00:00:00 2001 From: Henrique Date: Tue, 24 Nov 2020 17:11:01 +0100 Subject: [PATCH] regress: BEE Backport of CDP regress tests Author: Henrique Date: Mon Nov 18 23:46:16 2019 -0300 cdp: added 'group' Plugin Parameter Author: Henrique Date: Wed Nov 13 02:23:37 2019 -0300 cdp: added plugin param 'user' and fixed segfault on get_user_home_directory(...) Author: Henrique Date: Mon Nov 11 08:54:13 2019 -0300 cdp: fix 5494 about a crash when a invalid userHome param is specified Author: Henrique Date: Fri Nov 8 14:19:09 2019 -0300 regress: fixed Windows VSS Drives cdp unit test Author: Henrique Date: Tue Aug 6 20:28:07 2019 -0300 cdp: Added cdp-fd plugin to unit test scripts Author: Henrique Date: Tue Aug 6 19:53:19 2019 -0300 cdp: Fixed warnings and added win-test-process.exe to unit tests script Author: Henrique Date: Sun Aug 4 18:19:53 2019 -0300 cdp: Added script to make / execute Linux unit tests Author: Henrique Date: Fri Aug 2 10:30:00 2019 -0300 cdp-tests: 1) Merged makefiles for Linux and Windows 2) Fixed Linux compilation warnings 3) Simplified src directory structure Author: Henrique Date: Wed Jul 24 15:48:35 2019 -0300 cdp-plugin: tweak Author: Henrique Date: Tue Jul 23 22:39:34 2019 -0300 cdp-plugin: created regress script for Windows and enhanced script for Linux Author: Henrique Date: Wed Jul 17 14:23:03 2019 -0300 cdp-plugin: created regress script for linux Author: Henrique Date: Tue May 14 13:34:38 2019 -0300 cdp: tweak added regress tests for Windows Author: Henrique Date: Thu May 2 21:19:33 2019 -0300 cdp: Fix #4991 about userHome usage Author: Eric Bollengier Date: Tue Apr 23 17:16:16 2019 +0200 regress: Reformat cdp-client-tests.sh Author: Henrique Date: Thu Apr 11 09:59:00 2019 -0300 cdp-client: added regress test script --- regress/src/cdp/Makefile | 91 +++++ regress/src/cdp/Makefile.win64 | 97 +++++ regress/src/cdp/backupservice-test.cpp | 173 +++++++++ regress/src/cdp/cdp-plugin-test.cpp | 438 +++++++++++++++++++++ regress/src/cdp/folderwatcher-test.cpp | 273 +++++++++++++ regress/src/cdp/journal-test.cpp | 511 +++++++++++++++++++++++++ regress/src/cdp/win-test-process.c | 88 +++++ regress/tests/cdp-client-tests.sh | 149 +++++++ regress/tests/cdp-fd-plugin-test | 139 +++++++ regress/tests/cdp-unittests | 32 ++ 10 files changed, 1991 insertions(+) create mode 100644 regress/src/cdp/Makefile create mode 100644 regress/src/cdp/Makefile.win64 create mode 100644 regress/src/cdp/backupservice-test.cpp create mode 100644 regress/src/cdp/cdp-plugin-test.cpp create mode 100644 regress/src/cdp/folderwatcher-test.cpp create mode 100644 regress/src/cdp/journal-test.cpp create mode 100644 regress/src/cdp/win-test-process.c create mode 100755 regress/tests/cdp-client-tests.sh create mode 100755 regress/tests/cdp-fd-plugin-test create mode 100755 regress/tests/cdp-unittests diff --git a/regress/src/cdp/Makefile b/regress/src/cdp/Makefile new file mode 100644 index 000000000..fa3191533 --- /dev/null +++ b/regress/src/cdp/Makefile @@ -0,0 +1,91 @@ +# +# Copyright (C) 2000-2019 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# + +RMF = /bin/rm -f +CXX = /usr/bin/g++ +NO_ECHO = @ + +# +# Flags +# + +CXXFLAGS = -g -O2 -Wall + +# +# Paths +# + +COMMON_PATH = ../common +BUILD_PATH = ../../build +SRC_PATH = $(BUILD_PATH)/src +JOURNAL_PATH = $(SRC_PATH)/plugins/fd +FD_PLUGIN_PATH = $(SRC_PATH)/plugins/fd +FILED_PATH = $(SRC_PATH)/filed +CLIENT_PATH = $(SRC_PATH)/tools/cdp-client + +BIN_DIR = bin + +# +# Libs and Includes +# + +LIBS = -L$(SRC_PATH)/lib -L$(SRC_PATH)/findlib -lbac -lbacfind -lpthread -ldl + +INCLUDES = -I. -I$(COMMON_PATH) -I$(SRC_PATH) -I$(CLIENT_PATH) -I$(JOURNAL_PATH) -I$(SRC_PATH)/lib -I$(SRC_PATH)/findlib -I $(FILED_PATH) + +# +# Libtool specific settings +# + +LIBTOOL = $(BUILD_PATH)/libtool +LIBTOOL_COMPILE = $(LIBTOOL) --silent --tag=CXX --mode=compile +LIBTOOL_LINK = $(LIBTOOL) --silent --tag=CXX --mode=link +LIBTOOL_CLEAN = $(LIBTOOL) --silent --tag=CXX --mode=clean + +.SUFFIXES: .c .cpp .lo + +# inference rules +.c.lo: + @echo "Compiling $<" + $(NO_ECHO) $(LIBTOOL_COMPILE) $(CXX) -c $(CPPFLAGS) $(INCLUDES) $< +.cpp.lo: + @echo "Compiling $<" + $(NO_ECHO) $(LIBTOOL_COMPILE) $(CXX) -c $(CPPFLAGS) $(INCLUDES) $< + +#------------------------------------------------------------------------- + +all: Makefile test-dirs journal-test folderwatcher-test backupservice-test cdp-plugin-test + mkdir -p $(BIN_DIR) + @echo "==== Make of cdp-tests is good ====" + @echo " " + +test-dirs: + mkdir -p $(BIN_DIR) + +run-all: all + $(BIN_DIR)/journal-test + $(BIN_DIR)/folderwatcher-test + $(BIN_DIR)/backupservice-test + $(BIN_DIR)/cdp-plugin-test + +clean: + @$(RMF) -r $(BIN_DIR) .libs _libs ./tmp ./*.lo + +JTEST_OBJS = journal-test.lo $(JOURNAL_PATH)/journal.lo +journal-test: Makefile $(JTEST_OBJS) + $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(JTEST_OBJS) $(LIBS) + +FTEST_OBJS = folderwatcher-test.lo $(CLIENT_PATH)/folderwatcher.lo +folderwatcher-test: Makefile $(FTEST_OBJS) + $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(FTEST_OBJS) $(LIBS) + +BTEST_OBJS = backupservice-test.lo $(JOURNAL_PATH)/journal.lo $(CLIENT_PATH)/folderwatcher.lo $(CLIENT_PATH)/backupservice.lo +backupservice-test: Makefile $(BTEST_OBJS) + $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(BTEST_OBJS) $(LIBS) + +PTEST_OBJS = cdp-plugin-test.lo $(JOURNAL_PATH)/journal.lo +cdp-plugin-test: Makefile $(PTEST_OBJS) + $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -o $(BIN_DIR)/$@ $(PTEST_OBJS) $(LIBS) + diff --git a/regress/src/cdp/Makefile.win64 b/regress/src/cdp/Makefile.win64 new file mode 100644 index 000000000..d67e735ea --- /dev/null +++ b/regress/src/cdp/Makefile.win64 @@ -0,0 +1,97 @@ +# +# Copyright (C) 2000-2019 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# + +RMF = /bin/rm -f +CXX = x86_64-w64-mingw32-g++ +NO_ECHO = @ + +# +# Flags +# + +CXXFLAGS = -DHAVE_MINGW -DHAVE_WIN32 -DHAVE_MINGW_W64 -DHAVE_ZLIB_H -DHAVE_LIBZ -g -O2 -Wall -DUNICODE + +# +# Paths +# + +DEPKGS = ../../../depkgs-mingw-w64 +COMMON_PATH = ../common +BUILD_PATH = ../../build +SRC_PATH = $(BUILD_PATH)/src +JOURNAL_PATH = $(SRC_PATH)/plugins/fd +CDP_FD_PATH = $(SRC_PATH)/plugins/fd +CLIENT_PATH = $(SRC_PATH)/tools/cdp-client + +BIN_DIR = bin + +# +# Libs and Includes +# + +LIBS = -lwsock32 -lstdc++ -L../../../bacula/src/win32/release64 -lbacula -lpthread + +INCLUDES = -I. -I../../../bacula/src/ -I../../../bacula/src/filed -I$(COMMON_PATH) -I$(SRC_PATH) -I$(CLIENT_PATH) -I$(CDP_FD_PATH) -I$(JOURNAL_PATH) -I../../../bacula/src/win32/compat -I$(DEPKGS)/include/ -I$(DEPKGS)/include/pthreads + +# +# Libtool specific settings +# + +LINK = x86_64-w64-mingw32-g++ +LFLAGS = -static-libgcc -static-libstdc++ -mthreads -Wl,-enable-stdcall-fixup -Wl,-enable-auto-import -fno-strict-aliasing -Wl,-enable-runtime-pseudo-reloc -mconsole + +.SUFFIXES: .c .cpp .o + +# inference rules +.c.o: + @echo "Compiling $<" + $(NO_ECHO) $(CXX) -c $(CXXFLAGS) $(INCLUDES) $< +.cpp.o: + @echo "Compiling $<" + $(NO_ECHO) $(CXX) -c $(CXXFLAGS) $(INCLUDES) $< + +#------------------------------------------------------------------------- + +all: Makefile test-dirs backupservice-test.exe journal-test.exe folderwatcher-test.exe cdp-plugin-test.exe + @echo "==== Make of cdp-tests (WIN_64) is good ====" + @echo " " + +test-dirs: + mkdir -p $(BIN_DIR) + +clean: + @$(RMF) -r $(BIN_DIR) .libs _libs ./tmp ./*.o + +JTEST_OBJS = journal-test.o journal.o +journal-test.exe: Makefile $(JTEST_OBJS) win-test-process.exe + $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(JTEST_OBJS) $(LIBS) + +win-test-process.exe: Makefile win-test-process.o + $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ win-test-process.o $(LIBS) + +FTEST_OBJS = folderwatcher-test.o folderwatcher.o +folderwatcher-test.exe: Makefile $(FTEST_OBJS) + $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(FTEST_OBJS) $(LIBS) + +BTEST_OBJS = backupservice-test.o backupservice.o folderwatcher.o journal.o +backupservice-test.exe: Makefile $(BTEST_OBJS) + $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(BTEST_OBJS) $(LIBS) + +PTEST_OBJS = cdp-plugin-test.o journal.o +cdp-plugin-test.exe: Makefile $(PTEST_OBJS) + $(LINK) $(LFLAGS) -o $(BIN_DIR)/$@ $(PTEST_OBJS) $(LIBS) + +#------------------------------------------------------------------------- + +backupservice.o: $(CLIENT_PATH)/backupservice.h $(CLIENT_PATH)/backupservice.cpp + $(CXX) -c $(CXXFLAGS) $(INCLUDES) -o backupservice.o $(CLIENT_PATH)/backupservice.cpp + +journal.o: $(JOURNAL_PATH)/journal.c $(JOURNAL_PATH)/journal.h + $(CXX) -c $(CXXFLAGS) $(INCLUDES) -o journal.o $(JOURNAL_PATH)/journal.c + +folderwatcher.o: $(CLIENT_PATH)/folderwatcher.cpp $(CLIENT_PATH)/folderwatcher.h + $(CXX) -c $(CXXFLAGS) $(INCLUDES) -o folderwatcher.o $(CLIENT_PATH)/folderwatcher.cpp + + diff --git a/regress/src/cdp/backupservice-test.cpp b/regress/src/cdp/backupservice-test.cpp new file mode 100644 index 000000000..6ac762d58 --- /dev/null +++ b/regress/src/cdp/backupservice-test.cpp @@ -0,0 +1,173 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2004-2019 Bacula Systems SA + All rights reserved. + + The main author of Bacula is Kern Sibbald, with contributions from many + others, a complete list can be found in the file AUTHORS. + + Licensees holding a valid Bacula Systems SA license may use this file + and others of this release in accordance with the proprietary license + agreement provided in the LICENSE file. Redistribution of any part of + this release is not permitted. + + Bacula® is a registered trademark of Kern Sibbald. +*/ + +#include "backupservice.h" +#include "journal.h" +#include "btestutils.h" + +class BackupServiceTest +{ + char *_tmpDirPath; + char *_jPath; + char *_spoolPath; + Journal *_journal; + BackupService *_bservice; + +public: + BackupServiceTest() { + setupTitle("BackupServiceTest"); + + _tmpDirPath = BTU::getTmpPath(); + _spoolPath = BTU::concat(_tmpDirPath, "/cdp-spool-dir"); + _jPath = BTU::concat(_tmpDirPath, "/cdp-client.journal"); + + BTU::rmDir(_tmpDirPath); + BTU::mkDir(_tmpDirPath); + BTU::mkpath(_tmpDirPath, "cdp-spool-dir"); + + _journal = new Journal(); + _journal->setJournalPath(_jPath, _spoolPath); + _bservice = new BackupService(); + + logParam("Tmp Folder", _tmpDirPath); + logParam("Journal File", _jPath); + logParam("Spool Directory", _spoolPath); + + char *file1 = BTU::concat(_tmpDirPath, "/test_f1.txt"); + BTU::mkfile(file1); + } + + ~BackupServiceTest() { +// BTU::rmDir(_tmpDirPath); + } + +private: + +#ifndef HAVE_WIN32 + char *verifyFileWasCopied(const char *srcfpath, const char *srcfname, const char *destDirPath) { + struct dirent *de; + char *destFile = NULL; + DIR *destDir = opendir(destDirPath); + + if (destDir == NULL) { + printf("ERROR: Destination directory not found: %s", destDirPath); + exit(-1); + } + + while ((de = readdir(destDir)) != NULL) { + if (strstr(de->d_name, srcfname) != 0) { + destFile = bstrdup(de->d_name); + break; + } + } + + if (destFile == NULL) { + printf("ERROR: Destination file not found. Spool File not created for %s", srcfpath); + exit(-1); + } + + destFile = BTU::concat3(destDirPath, "/", destFile); + char *srcContents = BTU::readFile(srcfpath); + char *dstContents = BTU::readFile(destFile); + BTU::verifyStrings("File Copy in the Spool Dir", srcContents, dstContents); + closedir(destDir); + return destFile; + } + +#endif + + void verifyFileRecord(const char *fpath, const char *spoolfpath) { + _journal->beginTransaction("r"); + FileRecord *rec = _journal->readFileRecord(); + + if(rec == NULL) { + printf("FileRecord belonging to file %s was not found!", fpath); + exit(-1); + } + + BTU::verifyStrings("fname", fpath, rec->name); + BTU::verifyStrings("sname", spoolfpath, rec->sname); + BTU::verifyLength("mtime", rec->mtime, 10); + BTU::verifyNotNull("fattrs", rec->fattrs); + _journal->endTransaction(); + } + +public: + + void bservice_should_update_spool_dir() { + title("Service should update the Spool Directory" + " inside the Journal's Settings Record"); + + _bservice->start(_spoolPath, _journal); + SettingsRecord *rec = _journal->readSettings(); + BTU::verifyNotNull("Spool Dir", rec->getSpoolDir()); + BTU::verifyStrings("Spool Dir", _spoolPath, rec->getSpoolDir()); + + delete _bservice; + _bservice = new BackupService(); + } + + void bservice_should_save_file_and_record() { + title("Service should save changed file in the Spool Directory" + " and save the file's metadata in the Journal"); + + char *fname = (char *) "test_f1.txt"; + char *fpath = BTU::concat3(_tmpDirPath, "/", fname); + + _bservice->start(_spoolPath, _journal); + _bservice->onChange(fpath); + +#ifndef HAVE_WIN32 + char *spoolFilePath = verifyFileWasCopied(fpath, fname, _spoolPath); + verifyFileRecord(fpath, spoolFilePath); +#endif + } + + void bservice_should_stop_watching_folder() { + title("Service should stop watching folder"); + + FolderRecord rec; + rec.path = BTU::concat3(_tmpDirPath, "/", "folder1"); + BTU::mkDir(rec.path); + _journal->writeFolderRecord(rec); + + rec.path = BTU::concat3(_tmpDirPath, "/", "folder2"); + BTU::mkDir(rec.path); + _journal->writeFolderRecord(rec); + + rec.path = BTU::concat3(_tmpDirPath, "/", "folder3"); + BTU::mkDir(rec.path); + _journal->writeFolderRecord(rec); + + _bservice->start(_spoolPath, _journal); + _bservice->stopWatching("folder2"); + } + +}; + +int main(int argc, char *argv[]) +{ + lmgr_init_thread(); + BackupServiceTest test; + test.bservice_should_update_spool_dir(); + printf("OK\n"); + test.bservice_should_save_file_and_record(); + printf("OK\n"); + test.bservice_should_stop_watching_folder(); + printf("OK\n"); + lmgr_cleanup_thread(); +} diff --git a/regress/src/cdp/cdp-plugin-test.cpp b/regress/src/cdp/cdp-plugin-test.cpp new file mode 100644 index 000000000..9fbe4578d --- /dev/null +++ b/regress/src/cdp/cdp-plugin-test.cpp @@ -0,0 +1,438 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2004-2019 Bacula Systems SA + All rights reserved. + + The main author of Bacula is Kern Sibbald, with contributions from many + others, a complete list can be found in the file AUTHORS. + + Licensees holding a valid Bacula Systems SA license may use this file + and others of this release in accordance with the proprietary license + agreement provided in the LICENSE file. Redistribution of any part of + this release is not permitted. + + Bacula® is a registered trademark of Kern Sibbald. + */ + +#include "fd-plugin-driver.h" +#include "journal.h" +#include "cdp-fd.c" + +class CdpPluginTest +{ + char *_tmpDirPath; + char *_pluginPath; + char *_jobName; + char *_workingPath; + char *_spoolPath; + char *_jPath; + Journal _journal; + PluginTestDriver _d; + +public: + + void setPaths() { +#ifndef HAVE_WIN32 + _pluginPath = (char *) "./cdp-fd.so"; +#else + _pluginPath = (char *) "./cdp-fd.dll"; +#endif + + _tmpDirPath = BTU::getTmpPath(); + _workingPath = BTU::concat(_tmpDirPath, "/working"); + _spoolPath = BTU::concat(_tmpDirPath, "/spool-dir"); + _jPath = BTU::concat3(_tmpDirPath, "/", JOURNAL_CLI_FNAME); + _jobName = (char *) "my_test_job"; + } + + void makeTestFolders() { + BTU::rmDir(_tmpDirPath); + BTU::mkDir(_tmpDirPath); + BTU::mkDir(_workingPath); + BTU::mkDir(_spoolPath); + } + + void logParams() { + logParam("Plugin Path ", _pluginPath); + logParam("Test Dir ", _tmpDirPath); + logParam("Journal File ", _jPath); + logParam("Working Dir ", _workingPath); + logParam("Spool Dir ", _spoolPath); + logParam("Fake Job Name", _jobName); + } + + CdpPluginTest() { + setupTitle("CdpPluginTest"); + + OSDependentInit(); + this->setPaths(); + this->makeTestFolders(); + this->logParams(); + + _journal.setJournalPath(_jPath, _spoolPath); + _d.setWorkingDir(_workingPath); + debug_level = 60; + } + + ~CdpPluginTest() { +// BTU::rmDir(_tmpDirPath); + } + +private: + + void resetJournal() { + BTU::rmFile(_jPath); + _journal.setJournalPath(_jPath, _spoolPath); + } + + void makeFolderRecord(const char *fpath) { + FolderRecord rec; + rec.path = bstrdup(fpath); + _journal.writeFolderRecord(rec); + } + + FileRecord makeTestFile(const char *fname, const char *fcontents) { + FileRecord rec; + rec.name = BTU::concat3(_tmpDirPath, "/", fname); + BTU::mkfile(rec.name, fcontents); + rec.encode_attrs(); + + POOLMEM *tmp_sname = get_pool_memory(PM_FNAME); + Mmsg(tmp_sname, "%s/%d_%s", _spoolPath, rec.mtime, fname); + rec.sname = bstrdup(tmp_sname); + free_and_null_pool_memory(tmp_sname); + BTU::mkfile(rec.sname, fcontents); + + _journal.writeFileRecord(rec); + return rec; + } + + void verifySavePacket(FileRecord &rec, save_pkt &sp) { + POOLMEM *expected_fname = get_pool_memory(PM_FNAME); + rec.getBaculaName(expected_fname); + BTU::verifyStrings("SavePacket.sname", expected_fname, sp.fname); + BTU::verifyInt64("SavePacket.mtime", rec.mtime, (int64_t) sp.statp.st_mtime); + free_and_null_pool_memory(expected_fname); + } + + void sendBackupEvents() { + _d.setJobName(bstrdup(_jobName)); + bEvent jobStart = { bEventJobStart }; + _d.sendPluginEvent(jobStart, NULL); + + bEvent pluginComm = { bEventPluginCommand }; + POOLMEM *command = get_pool_memory(PM_FNAME); + Mmsg(command, "cdp: userHome=%s", _tmpDirPath); + bRC rc = _d.sendPluginEvent(pluginComm, (void *) command); + BTU::verifyBRC("Backup userHome param parsing", bRC_OK, rc); + free_and_null_pool_memory(command); + } + + void simulateBackup(FileRecord &rec, const char *fdata) { + struct save_pkt sp; + bRC rc = _d.startBackupFile(&sp); + BTU::verifyBRC("Start Backup RC", bRC_OK, rc); + verifySavePacket(rec, sp); + + _d.openFileIO(O_RDONLY); + _d.readFileIO(fdata, strlen(fdata)); + _d.closeFileIO(); + _d.endBackupFile(); + } + + void simulateRestore(const char *fname, const char *fc) { + struct restore_pkt rp; + rp.ofname = BTU::concat3(_tmpDirPath, "/", fname); + + bRC rc = _d.startRestoreFile(); + BTU::verifyBRC("Start Restore RC", bRC_Core, rc); + + rc = _d.createFile(&rp); + BTU::verifyBRC("Create File RC", bRC_OK, rc); + BTU::verifyInt("Create Status", CF_CORE, rp.create_status); + + _d.openFileIO(O_WRONLY); + _d.writeFileIO(fc); + _d.closeFileIO(); + _d.endRestoreFile(); + + char *fc2 = BTU::readFile(rp.ofname); + BTU::verifyStrings("Restored file contents", fc, fc2); + } + +public: + void plugin_should_handle_check_file() { + title("Plugin should have checkFile() function implemented"); + + subtitle("Plugin is NOT USED by Bacula - should always return bRC_OK"); + _d.startTest(_pluginPath); + + bRC rc = _d.checkFile("/tmp/foo/test_file.txt"); + BTU::verifyBRC("Check File RC", bRC_OK, rc); + + rc = _d.checkFile("/tmp/foo/test_file2.txt"); + BTU::verifyBRC("Check File RC", bRC_OK, rc); + + _d.endTest(); + + + subtitle("Plugin is USED by Bacula - should always return bRC_Seen"); + _d.startTest(_pluginPath); + + this->sendBackupEvents(); + + rc = _d.checkFile("/tmp/foo/test_file.txt"); + BTU::verifyBRC("Check File RC", bRC_Seen, rc); + + rc = _d.checkFile("/tmp/foo/test_file2.txt"); + BTU::verifyBRC("Check File RC", bRC_Seen, rc); + + _d.endTest(); + } + + void plugin_should_handle_backup_cycle() { + title("Plugin should handle Backup Cycle"); + + makeFolderRecord(_tmpDirPath); + + const char *fc1 = "akfkaklfass"; + FileRecord r1 = makeTestFile("test_file1.txt", fc1); + + const char *fc2 = "kajeçkfjafkçsjfçlasflsaç"; + FileRecord r2 = makeTestFile("my_cat.png", fc2); + + const char *fc3 = "jaeaij"; + FileRecord r3 = makeTestFile("funny_meme.gif", fc3); + char *ojc = BTU::readFile(_jPath); + + _d.startTest(_pluginPath); + + this->sendBackupEvents(); + + POOLMEM *migratedJournal = get_pool_memory(PM_FNAME); + Mmsg(migratedJournal, "%s/%s_0.journal", _workingPath, _jobName); + char *mjc = BTU::readFile(migratedJournal); + BTU::verifyNotNull("Migrated Journal", mjc); + BTU::verifyStrings("Migrated Journal", ojc, mjc); + + this->simulateBackup(r1, fc1); + this->simulateBackup(r2, fc2); + this->simulateBackup(r3, fc3); + + _d.tryBackupStop(); + _d.endTest(); + + BTU::verifyFileDoesntExist(migratedJournal); + } + + void plugin_should_handle_cancel_backup_event() { + POOLMEM *newJournal = get_pool_memory(PM_FNAME); + Mmsg(newJournal, "%s/%s_0.journal", _workingPath, _jobName); + + title("Plugin should handle cancel Backup event"); + + const char *fc1 = "akfkaklfass"; + FileRecord r1 = makeTestFile("test_file1.txt", fc1); + + const char *fc2 = "kajeçkfjafkçsjfçlasflsaç"; + FileRecord r2 = makeTestFile("my_cat.png", fc2); + + const char *fc3 = "jaeaij"; + FileRecord r3 = makeTestFile("funny_meme.gif", fc3); + + _d.startTest(_pluginPath); + + this->sendBackupEvents(); + this->simulateBackup(r1, fc1); + this->simulateBackup(r2, fc2); + + bEvent cancelEvent = { bEventCancelCommand }; + _d.sendPluginEvent(cancelEvent, NULL); + + _d.endTest(); + + BTU::verifyFileExists(newJournal); + BTU::rmDir(_workingPath); + BTU::mkpath(_tmpDirPath, "working"); + } + + void plugin_should_handle_restore_cycle() { + title("Plugin should handle Restore Cycle"); + + _d.startTest(_pluginPath); + + this->simulateRestore("rfile_t1.txt", "kjfsakjfsalk"); + this->simulateRestore("rfile_t2.txt", "jksakjfsakjfsalkyas7"); + this->simulateRestore("rfile_t3.txt", "kjfsak"); + + _d.endTest(); + } + + void plugin_should_handle_userhome_param() { + title("Plugin should handle \'userhome\' parameter"); + + _d.startTest(_pluginPath); + _d.setJobName(bstrdup(_jobName)); + bEvent jobStart = { bEventJobStart }; + _d.sendPluginEvent(jobStart, NULL); + + bEvent pluginComm = { bEventPluginCommand }; + bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: userhome=/root"); + CdpContext *pCtx = (CdpContext *) _d.ctx.pContext; + alist *userHomes = &(pCtx->userHomes); + alist *journals = &(pCtx->journals); + BTU::verifyBRC("User parsing", bRC_OK, rc); + BTU::verifyBiggerThan("User Homes", userHomes->size(), 0); + BTU::verifyBiggerThan("Journals", journals->size(), 0); + BTU::verifyInt64("Sizes should be the same", userHomes->size(), journals->size()); + BTU::verifyStrings("Expected User Home", "/root", (char *) (*userHomes)[0]); + + _d.endTest(); + } + + void plugin_should_handle_invalid_userhome_path() { + title("Plugin should handle invalid \'userHome\' path"); + + _d.startTest(_pluginPath); + + _d.setJobName(bstrdup(_jobName)); + bEvent jobStart = { bEventJobStart }; + _d.sendPluginEvent(jobStart, NULL); + + bEvent pluginComm = { bEventPluginCommand }; + bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: userHome=/non/existent/path"); + BTU::verifyBRC("userHome parsing", bRC_Error, rc); + + _d.endTest(); + } + + void plugin_should_handle_user_param() { + title("Plugin should handle \'user\' parameter"); + + _d.startTest(_pluginPath); + _d.setJobName(bstrdup(_jobName)); + bEvent jobStart = { bEventJobStart }; + _d.sendPluginEvent(jobStart, NULL); + + bEvent pluginComm = { bEventPluginCommand }; + bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: user=root"); + CdpContext *pCtx = (CdpContext *) _d.ctx.pContext; + alist *userHomes = &(pCtx->userHomes); + alist *journals = &(pCtx->journals); + BTU::verifyBRC("User parsing", bRC_OK, rc); + BTU::verifyBiggerThan("User Homes", userHomes->size(), 0); + BTU::verifyBiggerThan("Journals", journals->size(), 0); + BTU::verifyInt64("Sizes should be the same", userHomes->size(), journals->size()); + BTU::verifyStrings("Expected User Home", "/root", (char *) (*userHomes)[0]); + + _d.endTest(); + } + + void plugin_should_handle_invalid_user() { + title("Plugin should handle invalid \'user\' parameter"); + + _d.startTest(_pluginPath); + _d.setJobName(bstrdup(_jobName)); + bEvent jobStart = { bEventJobStart }; + _d.sendPluginEvent(jobStart, NULL); + + bEvent pluginComm = { bEventPluginCommand }; + bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: user=this-user-does-not-exit"); + BTU::verifyBRC("User parsing", bRC_Error, rc); + + _d.endTest(); + } + + void plugin_should_handle_group_param() { + title("Plugin should handle \'group\' parameter"); + + _d.startTest(_pluginPath); + _d.setJobName(bstrdup(_jobName)); + bEvent jobStart = { bEventJobStart }; + _d.sendPluginEvent(jobStart, NULL); + + bEvent pluginComm = { bEventPluginCommand }; + bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: group=sudo"); + CdpContext *pCtx = (CdpContext *) _d.ctx.pContext; + alist *userHomes = &(pCtx->userHomes); + alist *journals = &(pCtx->journals); + BTU::verifyBRC("Group parsing", bRC_OK, rc); + BTU::verifyBiggerThan("User Homes", userHomes->size(), 0); + BTU::verifyBiggerThan("Journals", journals->size(), 0); + BTU::verifyInt64("Sizes should be the same", userHomes->size(), journals->size()); + _d.endTest(); + } + + void plugin_should_handle_invalid_group() { + title("Plugin should handle invalid \'group\' parameter"); + + _d.startTest(_pluginPath); + _d.setJobName(bstrdup(_jobName)); + bEvent jobStart = { bEventJobStart }; + _d.sendPluginEvent(jobStart, NULL); + + bEvent pluginComm = { bEventPluginCommand }; + bRC rc = _d.sendPluginEvent(pluginComm, (void *) "cdp: group=non-exisent"); + CdpContext *pCtx = (CdpContext *) _d.ctx.pContext; + BTU::verifyBRC("Group parsing", bRC_Error, rc); + + _d.endTest(); + } + +#ifdef HAVE_WIN32 + void plugin_should_add_drivers_to_vss_snapshot() { + title("Plugin should add drivers to VSS Snapshot"); + + this->resetJournal(); + this->makeFolderRecord("A:/FolderA1"); + this->makeFolderRecord("A:/FolderA2/xyz/jkl"); + this->makeFolderRecord("D:/FolderD"); + this->makeFolderRecord("Z:/FolderZ1"); + this->makeFolderRecord("Z:/FolderZ2/jkl"); + this->makeFolderRecord("Z:/FolderZ3/abcd/ghi"); + + _d.startTest(_pluginPath); + + this->sendBackupEvents(); + + char drives[50]; + bEvent pluginComm = { bEventVssPrepareSnapshot }; + _d.sendPluginEvent(pluginComm, (void *) drives); + BTU::verifyStrings("VSS Drives List", "ADZ", drives); + + _d.endTest(); + } +#endif +}; + +int main(int argc, char *argv[]) +{ + CdpPluginTest test; + test.plugin_should_handle_check_file(); + printf("OK\n"); + test.plugin_should_handle_backup_cycle(); + printf("OK\n"); + test.plugin_should_handle_cancel_backup_event(); + printf("OK\n"); + test.plugin_should_handle_restore_cycle(); + printf("OK\n"); +#ifdef HAVE_WIN32 + test.plugin_should_add_drivers_to_vss_snapshot(); + printf("OK\n"); +#else + test.plugin_should_handle_userhome_param(); + printf("OK\n"); + test.plugin_should_handle_invalid_userhome_path(); + printf("OK\n"); + test.plugin_should_handle_user_param(); + printf("OK\n"); + test.plugin_should_handle_invalid_user(); + printf("OK\n"); + test.plugin_should_handle_group_param(); + printf("OK\n"); + test.plugin_should_handle_invalid_group(); + printf("OK\n"); +#endif +} + diff --git a/regress/src/cdp/folderwatcher-test.cpp b/regress/src/cdp/folderwatcher-test.cpp new file mode 100644 index 000000000..89f4d14ef --- /dev/null +++ b/regress/src/cdp/folderwatcher-test.cpp @@ -0,0 +1,273 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2004-2019 Bacula Systems SA + All rights reserved. + + The main author of Bacula is Kern Sibbald, with contributions from many + others, a complete list can be found in the file AUTHORS. + + Licensees holding a valid Bacula Systems SA license may use this file + and others of this release in accordance with the proprietary license + agreement provided in the LICENSE file. Redistribution of any part of + this release is not permitted. + + Bacula® is a registered trademark of Kern Sibbald. +*/ + +#include "folderwatcher.h" +#include "btestutils.h" + +class FolderWatcherTest: public FileChangeHandler +{ + char *_tmpDirPath; + FolderWatcher *_watcher; + alist _notifiedFiles; + + char *cpfile; + char *cpfile2; + +public: + FolderWatcherTest(): _notifiedFiles(100, false) { + setupTitle("FolderWatcherTest"); + + _tmpDirPath = BTU::getTmpPath(); + logParam("Tmp Folder", _tmpDirPath); + + BTU::rmDir(_tmpDirPath); + BTU::mkpath(_tmpDirPath, ""); + BTU::mkpath(_tmpDirPath, "test_rmdir1"); + BTU::mkpath(_tmpDirPath, "a1"); + BTU::mkpath(_tmpDirPath, "a1/b1"); + BTU::mkpath(_tmpDirPath, "a1/b1/c1"); + BTU::mkpath(_tmpDirPath, "a1/b1/c1/test_rmdir2"); + BTU::mkpath(_tmpDirPath, "a1/b1/c2"); + BTU::mkpath(_tmpDirPath, "a1/b2"); + BTU::mkpath(_tmpDirPath, "a1/b2/c1"); + BTU::mkpath(_tmpDirPath, "a1/b3"); + BTU::mkpath(_tmpDirPath, "a1/b3/c3"); + BTU::mkpath(_tmpDirPath, "a2"); + BTU::mkpath(_tmpDirPath, "a2/b3"); + BTU::mkpath(_tmpDirPath, "a2/b3/c3"); + + char *mvfile = BTU::concat(_tmpDirPath, "/test_mv1.txt"); + char *delfile = BTU::concat(_tmpDirPath, "/test_del1.txt"); + char *delfile2 = BTU::concat(_tmpDirPath, "/a1/b3/test_del2.txt"); + char *touchfile = BTU::concat(_tmpDirPath, "/a1/b2/test_touch1.txt"); + cpfile = BTU::concat(_tmpDirPath, "/test_cp1.txt"); + cpfile2 = BTU::concat(_tmpDirPath, "/a1/b3/test_cp2.txt"); + + BTU::mkfile(mvfile); + BTU::mkfile(delfile); + BTU::mkfile(delfile2); + BTU::mkfile(touchfile); + BTU::mkfile(cpfile); + BTU::mkfile(cpfile2); + + _watcher = new FolderWatcher(this); + char *err_msg = _watcher->watch(_tmpDirPath); + + if (err_msg != NULL) { + printf("%s", err_msg); + exit(-1); + } + } + + ~FolderWatcherTest() { + //BTU::rmDir(_tmpDirPath); + } + + void onChange(const char *fpath) { + _notifiedFiles.append((void *) bstrdup(fpath)); + } + +private: + + void verifyWatcherNotifiedChange(const char *fpath) { + time_t start = clock(); + while (difftime(clock(), start) < 5000) { + for (int i = 0; i < _notifiedFiles.size(); ++i) { + char *file = (char *) _notifiedFiles.get(i); + if (strcmp(file, fpath) == 0) { + printf("SUCCESS: notified %s\n\n", fpath); + return; + } + } + + usleep(1000); // 1 ms + } + + printf("ERROR: watcher did not notify %s\n", fpath); + exit(-1); + } + + void verifyWatcherNotifiedNothing() { + usleep(300000); // 300 ms + + if (_notifiedFiles.size() > 0) { + for (int i = 0; i < _notifiedFiles.size(); ++i) { + char *file = (char *) _notifiedFiles.get(i); + printf("ERROR: notified %s\n", file); + } + exit(-1); + } + + printf("SUCCESS: no notifications\n\n"); + } + +public: + + void clear() { + _notifiedFiles.destroy(); + } + + //TODO rename + void watcher_should_notify_creation_of_new_file() { + title("Watcher should notify creation of a new file"); + + subtitle("1"); + char *file = BTU::concat(_tmpDirPath, "/test_file.txt"); + BTU::mkfile(file); + verifyWatcherNotifiedChange(file); + + _notifiedFiles.destroy(); + + subtitle("2"); + char *file2 = BTU::concat(_tmpDirPath, "/a1/b1/test_file2.txt"); + BTU::mkfile(file2); + verifyWatcherNotifiedChange(file2); + + _notifiedFiles.destroy(); + + subtitle("3"); + char *file3 = BTU::concat(_tmpDirPath, "/a2/test_file3.txt"); + BTU::mkfile(file3); + verifyWatcherNotifiedChange(file3); + + _notifiedFiles.destroy(); + + subtitle("4"); + BTU::mkpath(_tmpDirPath, "a2/b3/new_folder"); + verifyWatcherNotifiedNothing(); + + char *file4 = BTU::concat(_tmpDirPath, "/a2/b3/new_folder/test_file4.txt"); + BTU::mkfile(file4); + verifyWatcherNotifiedChange(file4); + + _notifiedFiles.destroy(); + + subtitle("5"); + char *targetFile = BTU::concat(_tmpDirPath, "/a1/b2/copied1.txt"); + BTU::cpFile(cpfile, targetFile); + verifyWatcherNotifiedChange(targetFile); + + _notifiedFiles.destroy(); + + subtitle("6"); + targetFile = BTU::concat(_tmpDirPath, "/a2/b3/c3/copied2.txt"); + BTU::cpFile(cpfile2, targetFile); + verifyWatcherNotifiedChange(targetFile); + } + + void watcher_should_notify_nothing_on_file_removal() { + title("Watcher should notify nothing on file removal"); + + subtitle("1"); + char *file = BTU::concat(_tmpDirPath, "/test_del1.txt"); + BTU::rmFile(file); + verifyWatcherNotifiedNothing(); + + _notifiedFiles.destroy(); + + subtitle("2"); + char *file2 = BTU::concat(_tmpDirPath, "/a1/b3/test_del2.txt"); + BTU::rmFile(file2); + verifyWatcherNotifiedNothing(); + } + + void watcher_should_notify_changes_on_file() { + title("Watcher should notify changes on a file"); + +#ifndef HAVE_WIN32 + subtitle("1"); + char *file1 = BTU::concat(_tmpDirPath, "/a1/b2/test_touch1.txt"); + BTU::touch(file1); + verifyWatcherNotifiedChange(file1); + + _notifiedFiles.destroy(); + + subtitle("2"); + char *file2 = BTU::concat(_tmpDirPath, "/test_mv1.txt"); + char *file3 = BTU::concat(_tmpDirPath, "/a1/b1/c1/test_mv1_moved.txt"); + BTU::mvFile(file2, file3); + verifyWatcherNotifiedChange(file3); +#endif + } + + void watcher_should_notify_nothing_on_creation_of_new_dir() { + title("Watcher should notify nothing on creation of a new directory"); + + subtitle("1"); + BTU::mkpath(_tmpDirPath, "a42"); + verifyWatcherNotifiedNothing(); + + _notifiedFiles.destroy(); + + subtitle("2"); + BTU::mkpath(_tmpDirPath, "a100"); + verifyWatcherNotifiedNothing(); + + _notifiedFiles.destroy(); + + BTU::mkpath(_tmpDirPath, "a100/b57"); + verifyWatcherNotifiedNothing(); + } + + void watcher_should_notify_nothing_on_dir_removal() { + title("Watcher should notify nothing on directory removal"); + + subtitle("1"); + char *dir1 = BTU::concat(_tmpDirPath, "/test_rmdir1"); + BTU::rmDir(dir1); + verifyWatcherNotifiedNothing(); + + _notifiedFiles.destroy(); + + subtitle("2"); + char *dir2 = BTU::concat(_tmpDirPath, "/a1/b1/c1/test_rmdir2"); + BTU::rmDir(dir2); + verifyWatcherNotifiedNothing(); + } + + void watcher_should_not_notify_changes_on_dir_metadata() { + title("Watcher should notify nothing on directory metadata changes"); + + subtitle("1"); + char *dir = BTU::concat(_tmpDirPath, "/a1"); + BTU::touch(dir); + verifyWatcherNotifiedNothing(); + } + + void delete_watcher_should_stop_watch() { + title("Deleting watcher should stop watch"); + delete _watcher; + } +}; + +int main(int arg, char *argv[]) +{ + lmgr_init_thread(); + FolderWatcherTest test; + test.watcher_should_notify_creation_of_new_file(); + test.clear(); + test.watcher_should_notify_nothing_on_file_removal(); + test.clear(); + test.watcher_should_notify_changes_on_file(); + test.clear(); + test.watcher_should_notify_nothing_on_creation_of_new_dir(); + test.clear(); + test.watcher_should_notify_nothing_on_dir_removal(); + test.clear(); + test.watcher_should_not_notify_changes_on_dir_metadata(); + test.clear(); +} diff --git a/regress/src/cdp/journal-test.cpp b/regress/src/cdp/journal-test.cpp new file mode 100644 index 000000000..48bff84a7 --- /dev/null +++ b/regress/src/cdp/journal-test.cpp @@ -0,0 +1,511 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2004-2019 Bacula Systems SA + All rights reserved. + + The main author of Bacula is Kern Sibbald, with contributions from many + others, a complete list can be found in the file AUTHORS. + + Licensees holding a valid Bacula Systems SA license may use this file + and others of this release in accordance with the proprietary license + agreement provided in the LICENSE file. Redistribution of any part of + this release is not permitted. + + Bacula® is a registered trademark of Kern Sibbald. + */ + +#include "cdp.h" +#include "journal.h" +#include "btestutils.h" +#include "string.h" + +#ifndef HAVE_WIN32 +#include +#endif + +/** + Test thread used to confirm that the Journal class waits for the + Journal File to be available + */ +void *thread_journal(void *arg) { + printf("PARENT - thread_journal started....\n"); + Journal *journal = (Journal *) arg; + printf("PARENT - Calling beginTransaction()\n"); + journal->beginTransaction("r"); + printf("PARENT - beginTransaction() completed\n"); + return NULL; +} + + +class JournalTest +{ + + char *_tmpDirPath; + char *_jPath; + Journal *_journal; + + public: + JournalTest() { + setupTitle("Journal Tests"); + + _tmpDirPath = BTU::getTmpPath(); + _jPath = BTU::concat(_tmpDirPath, "/cdp-cli.journal"); + _journal = new Journal(); + + logParam("TmpDir", _tmpDirPath); + logParam("Journal File", _jPath); + + BTU::rmDir(_tmpDirPath); + BTU::mkDir(_tmpDirPath); + } + + ~JournalTest() { + // BTU::rmDir(_tmpDirPath); + } + + private: + + void verifyFileContainsRecord(const char *fname, const FileRecord &rec) { + const int SANITY_CHECK = 10000; + char *fcontents = BTU::readFile(fname); + char expected[SANITY_CHECK]; + bsnprintf(expected, SANITY_CHECK, + "File {" NL + "name=%s" NL + "sname=%s" NL + "mtime=%lld" NL + "attrs=%s" NL + "}" NL, + rec.name, + rec.sname, + rec.mtime, + rec.fattrs); + if (strstr(fcontents, expected) == NULL) { + printf("ERROR: FileRecord of file %s not found.\n" + "FCONTENTS:\n%s\n" + "EXPECTED:\n%s\n", rec.name, fcontents, expected); + exit(-1); + } + } + + void verifyFolderNotContainsRecord(const char *fname, const FolderRecord &rec) { + const int SANITY_CHECK = 1000000; + char *fcontents = BTU::readFile(fname); + char expected[SANITY_CHECK]; + bsnprintf(expected, SANITY_CHECK, + "Folder {" NL + "path=%s" NL + "}" NL, + rec.path); + if (strstr(fcontents, expected) != NULL) { + printf("ERROR: FolderRecord of folder %s not found.", rec.path); + exit(-1); + } + } + + void verifyFolderContainsRecord(const char *fname, const FolderRecord &rec) { + const int SANITY_CHECK = 1000000; + char *fcontents = BTU::readFile(fname); + char expected[SANITY_CHECK]; + bsnprintf(expected, SANITY_CHECK, + "Folder {" NL + "path=%s" NL + "}" NL, + rec.path); + if (strstr(fcontents, expected) == NULL) { + printf("ERROR: FolderRecord of folder %s not found.", rec.path); + exit(-1); + } + } + + void do_update_settings_test(SettingsRecord &rec) { + if (!_journal->writeSettings(rec)) { + printf("ERROR: could not write SettingsRecord"); + exit(-1); + } + SettingsRecord *read = _journal->readSettings(); + BTU::verifyNotNull("SettingsRecord", read); + BTU::verifyInt64("jversion", rec.journalVersion, read->journalVersion); + BTU::verifyInt64("heartbeat", rec.heartbeat, read->heartbeat); + BTU::verifyStrings("spooldir", rec.getSpoolDir(), read->getSpoolDir()); + delete read; + delete read; + } + + void do_write_record_test(const char* fname) { + FileRecord rec; + rec.name = BTU::concat3(_tmpDirPath, "/" ,fname); + char *tmp = BTU::concat("12345_", fname); + rec.sname = BTU::concat3(_tmpDirPath, "/" , tmp); + BTU::mkfile(rec.name); + + if (!rec.encode_attrs()) { + printf("ERROR: could not encode file attributes: %s", rec.name); + exit(-1); + } + + if (!_journal->writeFileRecord(rec)) { + printf("ERROR: could not write FileRecord: %s", rec.name); + exit(-1); + } + verifyFileContainsRecord(_jPath, rec); + } + + void do_write_folder_test(const char* fname) { + FolderRecord rec; + rec.path = BTU::concat3(_tmpDirPath, "/" ,fname); + if (!_journal->writeFolderRecord(rec)) { + printf("ERROR: could not write FolderRecord: %s", fname); + exit(-1); + } + verifyFolderContainsRecord(_jPath, rec); + } + + void do_read_file_test(const char* fname) { + FileRecord *fr = _journal->readFileRecord(); + + if(fname != NULL) { + char *fpath = BTU::concat3(_tmpDirPath, "/", fname); + BTU::verifyStrings("File Path", fpath, fr->name); + free(fpath); + } else { + BTU::verifyNull("FileRecord", fr); + } + + delete fr; + } + + void do_read_folder_test(const char* folder) { + FolderRecord *fr = _journal->readFolderRecord(); + + if(fr == NULL && folder != NULL) { + printf("ERROR: expected a non-null FolderRecord"); + exit(-1); + } else if(folder != NULL) { + char *fpath = BTU::concat3(_tmpDirPath, "/", folder); + BTU::verifyStrings("File Path", fpath, fr->path); + free(fpath); + } else { + BTU::verifyNull("FolderRecord", fr); + } + + delete fr; + } + + void do_remove_folder_test(const char* folder) { + FolderRecord rec; + rec.path = BTU::concat3(_tmpDirPath, "/" , folder); + if (!_journal->removeFolderRecord(rec.path)) { + printf("ERROR: could not remove FolderRecord: %s", rec.path); + exit(-1); + } + + verifyFolderNotContainsRecord(_jPath, rec); + } + + public: + + void journal_filerecord_should_encode_and_decode_file_attributes() { + title("File Record should encode and decode attributes"); + + FileRecord rec; + struct stat sbuf; + + rec.name = BTU::concat3(_tmpDirPath, "/", "testde.txt"); + BTU::mkfile(rec.name, "çasfkas"); + + if (!rec.encode_attrs()) { + printf("ERROR: could not encode attributes of file %s", rec.name); + exit(-1); + } + + rec.decode_attrs(sbuf); + BTU::verifyInt64("mtime", rec.mtime, (int64_t) sbuf.st_mtime); + } + + void journal_should_create_jfile_on_set_path() { + title("setJournalPath() should create a new Journal File" + " with a default SettingsRecord" + " if this File does not exist"); + + if (!_journal->setJournalPath(_jPath)) { + printf("ERROR: could not set journal path to %s", _jPath); + exit(-1); + } + + SettingsRecord *rec = _journal->readSettings(); + BTU::verifyNotNull("SettingsRecord", rec); + BTU::verifyInt64("heartbeat", -1, rec->heartbeat); + BTU::verifyInt64("jversion", JOURNAL_VERSION, rec->journalVersion); + } + + // void journal_should_update_settings_record() { + // title("writeSettings() should update Settings Record"); + + // char *spath = CDP::spoolDir(); + // logParam("Test Spool Dir", spath); + + // SettingsRecord r1; + // r1.heartbeat = 500000; + // r1.journalVersion = 2; + // r1.spoolDir = (char *) "/home/foo/bar"; + // do_update_settings_test(r1); + + // SettingsRecord r2; + // r2.heartbeat = 999999; + // r2.journalVersion = 40; + // do_update_settings_test(r2); + // } + + void journal_should_add_new_file_records() { + title("Journal should add new File Records"); + + do_write_record_test("file_test1.txt"); + do_write_record_test("file_test2.pdf"); + do_write_record_test("file_test3.png"); + do_write_record_test("file_test4.odt"); + } + + void journal_should_add_new_folder_records() { + title("Journal should add new Folder Records"); + + do_write_folder_test("folder1"); + do_write_folder_test("folder2"); + do_write_folder_test("folder3"); + } + + void journal_should_add_new_file_records_2() { + title("Journal should add new File Records (2)"); + + do_write_record_test("file_test5.gif"); + do_write_record_test("file_test6.cpp"); + } + + void journal_should_add_new_folder_records_2() { + title("Journal should add new Folder Records (2)"); + + do_write_folder_test("folder4"); + do_write_folder_test("folder5"); + } + + void journal_should_read_all_file_records() { + title("Journal should read all File Records"); + + _journal->beginTransaction("r"); + do_read_file_test("file_test1.txt"); + do_read_file_test("file_test2.pdf"); + do_read_file_test("file_test3.png"); + do_read_file_test("file_test4.odt"); + do_read_file_test("file_test5.gif"); + do_read_file_test("file_test6.cpp"); + do_read_file_test(NULL); + _journal->endTransaction(); + } + + void journal_should_read_all_folder_records() { + title("Journal should read all Folder Records"); + + _journal->beginTransaction("r"); + do_read_folder_test("folder1"); + do_read_folder_test("folder2"); + do_read_folder_test("folder3"); + do_read_folder_test("folder4"); + do_read_folder_test("folder5"); + do_read_folder_test(NULL); + _journal->endTransaction(); + } + + void journal_should_remove_folder_records() { + title("Journal should remove Folder Records"); + + char *oldJournal = BTU::concat(_tmpDirPath, "/old.journal"); + BTU::cpFile(_jPath, oldJournal); + + do_remove_folder_test("folder3"); + do_remove_folder_test("folder4"); + do_remove_folder_test("folder1"); + do_remove_folder_test("folder2"); + + BTU::rmFile(_jPath); + BTU::cpFile(oldJournal, _jPath); + } + + void journal_should_migrate_to_new_path() { + title("Journal should migrate to New Path"); + + char *f1 = BTU::readFile(_jPath); + char *newPath = BTU::concat(_tmpDirPath, "/migrated.journal"); + + if (!_journal->migrateTo(newPath)) { + printf("ERROR: could not migrate Journal to %s", newPath); + exit(-1); + } + + // Verify that the migrated Journal has the same contents + // as the old one + char *f2 = BTU::readFile(newPath); + BTU::verifyNotNull("Migrated Journal", f2); + BTU::verifyStrings("Migrated Journal", f1, f2); + free(f1); + free(f2); + + // Verify that the old Journal now only have: + // 1-) SettingsRecord + // 2-) FolderRecords + _journal = new Journal(); + _journal->setJournalPath(_jPath); + + SettingsRecord *rec = _journal->readSettings(); + BTU::verifyNotNull("SettingsRecord", rec); + BTU::verifyInt64("jversion", 1, rec->journalVersion); + BTU::verifyInt64("heartbeat", -1, rec->heartbeat); + + _journal->beginTransaction("r"); + do_read_folder_test("folder1"); + do_read_folder_test("folder2"); + do_read_folder_test("folder3"); + do_read_folder_test("folder4"); + do_read_folder_test("folder5"); + do_read_folder_test(NULL); + _journal->endTransaction(); + + _journal->beginTransaction("r"); + do_read_file_test(NULL); + _journal->endTransaction(); + } + + void journal_should_handle_errors_on_reading() { + title("Journal should handle errors while reading a Record"); + + subtitle("extract_val() subroutine should handle invalid key-value pairs"); + char *val; + + // No '=' character + val = _journal->extract_val("noequalssign\n"); + BTU::verifyNull("extract_val() test", val); + + // No '\n' character + val = _journal->extract_val("noendline=true"); + BTU::verifyNull("extract_val() test", val); + + // No '=' and '\n' character + val = _journal->extract_val("no_both"); + BTU::verifyNull("extract_val() test", val); + } + + void journal_should_wait_for_jfile_to_be_available() { + title("Journal should wait for Jornal File to be available"); + +#ifndef HAVE_WIN32 + pid_t pid; + pid = fork(); + + if (pid < 0) { + printf("ERROR: Could not create Test Process\n"); + exit(-1); + } + + if (pid == 0) { + printf("Created Child Process\n"); + FILE *fp = bfopen(_jPath, "r"); + int fd = fileno(fp); + printf("CHILD - got lock for Journal File\n"); + flock(fd, LOCK_EX); + sleep(3); + printf("CHILD - released lock for Journal File\n"); + fclose(fp); + exit(0); + } else { + sleep(1); + pthread_t jthread; + pthread_create(&jthread, NULL, thread_journal, (void *) _journal); + if (_journal->hasTransaction) { + printf("ERROR: Journal didn't respect the File Lock \n"); + exit(-1); + } + pthread_join(jthread, NULL); + if (!_journal->hasTransaction) { + printf("ERROR: Journal didn't have a transaction after the Journal File was available \n"); + exit(-1); + } + } +#else + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + POOLMEM *_jPathWrapper = get_pool_memory(PM_FNAME); + Mmsg(_jPathWrapper, "c:\\Program Files\\Bacula\\win_test_process.exe \"%s\"", _jPath); + const size_t wsize = (strlen(_jPathWrapper) + 1) * 2; + wchar_t *wjpath = new wchar_t[wsize]; + mbstowcs(wjpath, (char *) _jPathWrapper, wsize); + + if(!CreateProcess(NULL, // No module name (use command line) + wjpath, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi ) // Pointer to PROCESS_INFORMATION structure + ) + { + printf( "CreateProcess failed (%lu).\n", GetLastError()); + return; + } + + sleep(1); + _journal->beginTransaction("r"); + if (!_journal->hasTransaction) { + printf("ERROR: Journal didn't have a transaction after the Journal File was available \n"); + exit(-1); + } + + // Wait until child process exits. + WaitForSingleObject(pi.hProcess, INFINITE); + + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + +#endif + } + +}; + +int main(int argc, char *argv[]) +{ + lmgr_init_thread(); + JournalTest test; + test.journal_filerecord_should_encode_and_decode_file_attributes(); + printf("OK\n"); + test.journal_should_create_jfile_on_set_path(); + printf("OK\n"); + //TODO fix update settings; + //test.journal_should_update_settings_record(); + test.journal_should_add_new_file_records(); + printf("OK\n"); + test.journal_should_add_new_folder_records(); + printf("OK\n"); + test.journal_should_add_new_file_records_2(); + printf("OK\n"); + test.journal_should_add_new_folder_records_2(); + printf("OK\n"); + test.journal_should_read_all_file_records(); + printf("OK\n"); + test.journal_should_read_all_folder_records(); + printf("OK\n"); + test.journal_should_remove_folder_records(); + printf("OK\n"); + test.journal_should_migrate_to_new_path(); + printf("OK\n"); + test.journal_should_handle_errors_on_reading(); + printf("OK\n"); + test.journal_should_wait_for_jfile_to_be_available(); + printf("OK\n"); +} diff --git a/regress/src/cdp/win-test-process.c b/regress/src/cdp/win-test-process.c new file mode 100644 index 000000000..c08884751 --- /dev/null +++ b/regress/src/cdp/win-test-process.c @@ -0,0 +1,88 @@ +#include "bacula.h" + +static BOOL +file_size (HANDLE h, DWORD * lower, DWORD * upper) +{ + *lower = GetFileSize (h, upper); + return 1; +} + +/* LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. */ +# ifndef LOCKFILE_FAIL_IMMEDIATELY +# define LOCKFILE_FAIL_IMMEDIATELY 1 +# endif + +/* Acquire a lock. */ +static BOOL +do_lock (HANDLE h, int non_blocking, int exclusive) +{ + BOOL res; + DWORD size_lower, size_upper; + OVERLAPPED ovlp; + int flags = 0; + + /* We're going to lock the whole file, so get the file size. */ + res = file_size (h, &size_lower, &size_upper); + if (!res) + return 0; + + /* Start offset is 0, and also zero the remaining members of this struct. */ + memset (&ovlp, 0, sizeof ovlp); + + if (non_blocking) + flags |= LOCKFILE_FAIL_IMMEDIATELY; + if (exclusive) + flags |= LOCKFILE_EXCLUSIVE_LOCK; + + return LockFileEx (h, flags, 0, size_lower, size_upper, &ovlp); +} + +/* Unlock reader or exclusive lock. */ +static BOOL +do_unlock (HANDLE h) +{ + int res; + DWORD size_lower, size_upper; + + res = file_size (h, &size_lower, &size_upper); + if (!res) + return 0; + + return UnlockFile (h, 0, 0, size_lower, size_upper); +} + +int main(int argc, char *argv[]) +{ + printf("Created Child Process\n"); + FILE *fp = bfopen(argv[1], "r"); + + if (!fp) { + printf("ERROR: could not open file %s\n", argv[1]); + exit(-1); + } + + int fd = fileno(fp); + HANDLE handle = (HANDLE) _get_osfhandle (fd); + + if (handle == INVALID_HANDLE_VALUE) { + printf("ERROR: could not get File Handle: %s\n", argv[1]); + exit(-1); + } + + if (!do_lock(handle, 1, 1)) { + printf("ERROR: could not lock file: %s\n", argv[1]); + exit(-1); + } + + printf("CHILD - got lock for Journal File\n"); + sleep(3); + + if (!do_unlock(handle)) { + printf("ERROR: could not lock file: %s\n", argv[1]); + exit(-1); + } + + printf("CHILD - released lock for Journal File\n"); + fclose(fp); + return 0; +} diff --git a/regress/tests/cdp-client-tests.sh b/regress/tests/cdp-client-tests.sh new file mode 100755 index 000000000..4fc150b03 --- /dev/null +++ b/regress/tests/cdp-client-tests.sh @@ -0,0 +1,149 @@ +#!/bin/sh + +export REGRESS_DEBUG=1 +. scripts/functions + +TestName="cdp-client-tests.sh" +Path=${cwd} +CdpClientPath=$Path/build/src/tools/cdp-client/cdp-client +TmpDir="$Path/tmp" +SpoolDir=$TmpDir/"spool-dir" +JournalFile=$TmpDir/".bcdp-cli.journal" +WatchedDir1=$TmpDir/"folder1" +WatchedDir2=$TmpDir/"folder2" + +make -C $Path/build/src/tools/cdp-client/ install > /dev/null + +start_test + +run_cdp_client() +{ +Args="-d 999 -s $SpoolDir -j $TmpDir -f $WatchedDir1 -f $WatchedDir2" + if [ -f "$CdpClientPath" ] + then + $CdpClientPath $Args > $TmpDir/"cdp.log" & + cdppid=$! + trap "kill $cdppid" EXIT TERM INT + else + print_debug "CDP Client not found: $CdpClientPath. Please, make the binary." + fi +sleep 0.4 +} + +mkfile_and_wait() +{ +echo 'abcde' > $1 +sleep 0.4 +} + +mkdir_and_wait() +{ +mkdir -p $1 +sleep 0.4 +} + +touch_and_wait() +{ +touch $1 +sleep 0.4 +} + +cp_and_wait() +{ +cp $1 $2 +sleep 0.4 +} + +mv_and_wait() +{ +mv $1 $2 +sleep 0.4 +} + +verify_spooldir_contains_file() +{ +print_debug "Verifying if Spool Dir contains: $1" +FoundFile=$(find $SpoolDir | grep -o "$1" | wc -l) +if [ "$FoundFile" -eq "1" ]; then + print_debug "OK" +else + print_debug "FAILED" + estat=1 +fi +} + +verify_journal_contains_record() +{ +print_debug "Verifying if Journal File contains File Record" +JournalHasFile=$(cat $JournalFile | grep "name=$1" | wc -l) +if [ "$JournalHasFile" -eq "1" ]; then + print_debug "OK" +else + print_debug "FAILED" + estat=1 +fi +} + +rm -rf $TmpDir +mkdir $TmpDir +mkdir $WatchedDir1 +mkdir $WatchedDir2 +SedTestFile=$WatchedDir2/"sed_test.txt" +mkfile_and_wait $SedTestFile +TouchTestFile=$WatchedDir2/"touch_test.txt" + +run_cdp_client + +print_debug "TEST 1: CDP Client should detect creation of a new file:" + +TestFile1=$WatchedDir1/"test1.txt" +print_debug "EXAMPLE 1: Creation of file $TestFile1" +mkfile_and_wait $TestFile1 +verify_spooldir_contains_file "[0-9]\{10\}_test1.txt" +verify_journal_contains_record "$TestFile1" + +TestFile2=$WatchedDir1/"test2.txt" +print_debug "EXAMPLE 2: Creation of file $TestFile2" +mkfile_and_wait $TestFile2 +verify_spooldir_contains_file "[0-9]\{10\}_test2.txt" +verify_journal_contains_record "$TestFile2" + +NewPath=$WatchedDir2/"my/new/path" +TestFile3=$NewPath/"test3.txt" +print_debug "EXAMPLE 3: Creation of file $TestFile3 after creation of new path $NewPath" + +mkdir_and_wait $NewPath +mkfile_and_wait $TestFile3 +verify_spooldir_contains_file "[0-9]\{10\}_test3.txt" +verify_journal_contains_record "$TestFile3" + +TestFileCopy=$WatchedDir2/"test1_copy.txt" +print_debug "EXAMPLE 4: Copy of file $TestFile1 into $TestFileCopy" +cp_and_wait $TestFile1 $TestFileCopy +verify_spooldir_contains_file "[0-9]\{10\}_test1_copy.txt" +verify_journal_contains_record $TestFileCopy + +print_debug "---------------------------------------------------------------------------" +print_debug "TEST 2: CDP Client should detect file changes:" + +TestFile1=$WatchedDir2/"test_mv.gif" +TestFileMove=$WatchedDir1/"test_mv_renamed.gif" +print_debug "EXAMPLE 1: Move file $TestFile1 into $TestFileMove" +mkfile_and_wait $TestFile1 +mv_and_wait $TestFile1 $TestFileMove +verify_spooldir_contains_file "[0-9]\{10\}_test_mv_renamed.gif" +verify_journal_contains_record $TestFileMove + +print_debug "EXAMPLE 2: Sed file $SedTestFile" +sed -i "$ a #newline" $SedTestFile +sleep 0.4 +verify_spooldir_contains_file "[0-9]\{10\}_sed_test.txt" +verify_journal_contains_record $SedTestFile + +print_debug "EXAMPLE 3: Touch on file $TouchTestFile" +mkfile_and_wait $TouchTestFile +touch_and_wait $TouchTestFile +verify_spooldir_contains_file "[0-9]\{10\}_touch_test.txt" +verify_journal_contains_record $TouchTestFile + +end_test diff --git a/regress/tests/cdp-fd-plugin-test b/regress/tests/cdp-fd-plugin-test new file mode 100755 index 000000000..f7b5ab01b --- /dev/null +++ b/regress/tests/cdp-fd-plugin-test @@ -0,0 +1,139 @@ +#!/bin/sh + +#export REGRESS_DEBUG=1 + +TestName="cdp-plugin-test" +JobName="PluginCdpTest" +. scripts/functions +UserHome="$tmp/testUserHome" +WatchedDir="$UserHome/watched-dir" +SpoolDir="$UserHome/spool-dir" +RestoreDir="$tmp/brestores" + +make_cdp_plugin() +{ +(cd $src/src/plugins/fd; make cdp) +cp -a build/src/plugins/fd/.libs/cdp-fd.so $bin/plugins +} + +write_tests_bcommands() +{ +cat <$tmp/bconcmds +@output /dev/null +messages +@$out $tmp/log_backup.out +setdebug level=4 storage=File1 +label volume=TestVolume001 storage=File1 pool=Default slot=1 drive=0 +show job=$JobName +run job=$JobName yes +status storage=File1 +@sleep 1 +@sleep 1 +wait +messages +list files jobid=1 +@# +@# now do a restore +@# +@$out $tmp/log_restore.out +setdebug level=4 storage=File1 +restore where=$RestoreDir select all done +yes +wait +messages +quit +END_OF_DATA +} + +create_journal() +{ +cat < $UserHome/.bcdp-cli.journal +Settings { +spooldir=$SpoolDir +heartbeat=-1 +jversion=1 +} +Folder { +path=$WatchedDir +} +File { +name=$WatchedDir/test_file1 +sname=$SpoolDir/1553697755_test_file1 +atime=1553697755 +attrs=gE VBM6 IGk B Po Po A b BAA I Bcm4vb Bcm4vb Bcm4vb A A A +} +File { +name=$WatchedDir/test_file2 +sname=$SpoolDir/1553697777_test_file2 +atime=1553697777 +attrs=gE VBM6 IGk B Po Po A b BAA I Bcm4vb Bcm4vb Bcm4vb A A A +} +File { +name=$WatchedDir/test_file3 +sname=$SpoolDir/1553697799_test_file3 +atime=1553697799 +attrs=gE VBM6 IGk B Po Po A b BAA I Bcm4vb Bcm4vb Bcm4vb A A A +} +END_OF_DATA +} + +create_testfiles() +{ +#If you change the file size, remember to change it's attributes as well +echo "kflasflsalfsaklsaffsfasfsa" > $WatchedDir/test_file1 +echo "kflasflsalfsaklsaffsfasfsa" > $SpoolDir/1553697755_test_file1 + +echo "kflasflsalf18sdakdjsakl121" > $WatchedDir/test_file2 +echo "kflasflsalf18sdakdjsakl121" > $SpoolDir/1553697777_test_file2 + +echo "kflasa9s6dsa968dsa986d1289" > $WatchedDir/test_file3 +echo "kflasa9s6dsa968dsa986d1289" > $SpoolDir/1553697799_test_file3 +} + +verify_backup_ok() +{ +nb=`grep 'Backup OK' $tmp/log_backup.out | wc -l` +if [ $nb -lt 1 ]; then + print_debug "ERROR: should have found Backup OK message" + bstat=1 +fi +} + +verify_restore_ok() +{ +nb=`grep 'Restore OK' $tmp/log_restore.out | wc -l` +if [ $nb -lt 1 ]; then + print_debug "ERROR: should have found Restore OK message" + bstat=1 +fi + +backup_count=`find $UserHome -type f | wc -l` +#Do not count the Journal, which was not backed up +backup_count=$((backup_count-1)) +restore_count=`find $RestoreDir$UserHome -type f | wc -l` + +if [ $backup_count != $restore_count ]; then + print_debug "ERROR: File count mismatch. Backup count: $backup_count. Restore Count: $restore_count" +fi +} + +scripts/cleanup +scripts/copy-cdp-plugin-confs + +start_test + +make_cdp_plugin +mkdir $UserHome +mkdir $SpoolDir +mkdir $WatchedDir +create_journal +create_testfiles + +write_tests_bcommands +run_bacula +stop_bacula + +verify_backup_ok +verify_restore_ok + +end_test diff --git a/regress/tests/cdp-unittests b/regress/tests/cdp-unittests new file mode 100755 index 000000000..b3f0315c8 --- /dev/null +++ b/regress/tests/cdp-unittests @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Copyright (C) 2000-2019 Kern Sibbald +# License: BSD 2-Clause; see file LICENSE-FOSS +# +# CDP Unit Tests +# + +. scripts/functions + +BIN_DIR="$unitsrc/cdp/bin" + +exec_bin() +{ +LOGFILE=$tmp/$1.log +(cd $BIN_DIR; ./$1 > $LOGFILE) + +nb=`grep 'ERROR' $LOGFILE | wc -l` +if [ $nb -gt 0 ]; then + print_debug "ERROR: One or more tests failed." + exit 1 +fi +} + +(cd $src/src/plugins/fd; make cdp) +(cd $unitsrc/cdp; make) +cp -a $src/src/plugins/fd/.libs/cdp-fd.so $unitsrc/cdp/bin + +exec_bin "journal-test" +exec_bin "folderwatcher-test" +exec_bin "backupservice-test" +exec_bin "cdp-plugin-test" -- 2.47.3