]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
regress: BEE Backport of CDP regress tests
authorHenrique <henrique.faria@baculasystems.com>
Tue, 24 Nov 2020 16:11:01 +0000 (17:11 +0100)
committerEric Bollengier <eric@baculasystems.com>
Thu, 24 Mar 2022 08:03:27 +0000 (09:03 +0100)
Author: Henrique <henrique.faria@baculasystems.com>
Date:   Mon Nov 18 23:46:16 2019 -0300

    cdp: added 'group' Plugin Parameter

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Wed Nov 13 02:23:37 2019 -0300

    cdp: added plugin param 'user' and fixed segfault on get_user_home_directory(...)

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Mon Nov 11 08:54:13 2019 -0300

    cdp: fix 5494 about a crash when a invalid userHome param is specified

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Fri Nov 8 14:19:09 2019 -0300

    regress: fixed Windows VSS Drives cdp unit test

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Tue Aug 6 20:28:07 2019 -0300

    cdp: Added cdp-fd plugin to unit test scripts

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Tue Aug 6 19:53:19 2019 -0300

    cdp: Fixed warnings and added win-test-process.exe to unit tests script

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Sun Aug 4 18:19:53 2019 -0300

    cdp: Added script to make / execute Linux unit tests

Author: Henrique <henrique.faria@baculasystems.com>
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 <henrique.faria@baculasystems.com>
Date:   Wed Jul 24 15:48:35 2019 -0300

    cdp-plugin: tweak

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Tue Jul 23 22:39:34 2019 -0300

    cdp-plugin: created regress script for Windows and enhanced script for Linux

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Wed Jul 17 14:23:03 2019 -0300

    cdp-plugin: created regress script for linux

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Tue May 14 13:34:38 2019 -0300

    cdp: tweak added regress tests for Windows

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Thu May 2 21:19:33 2019 -0300

    cdp: Fix #4991 about userHome usage

Author: Eric Bollengier <eric@baculasystems.com>
Date:   Tue Apr 23 17:16:16 2019 +0200

    regress: Reformat cdp-client-tests.sh

Author: Henrique <henrique.faria@baculasystems.com>
Date:   Thu Apr 11 09:59:00 2019 -0300

    cdp-client: added regress test script

regress/src/cdp/Makefile [new file with mode: 0644]
regress/src/cdp/Makefile.win64 [new file with mode: 0644]
regress/src/cdp/backupservice-test.cpp [new file with mode: 0644]
regress/src/cdp/cdp-plugin-test.cpp [new file with mode: 0644]
regress/src/cdp/folderwatcher-test.cpp [new file with mode: 0644]
regress/src/cdp/journal-test.cpp [new file with mode: 0644]
regress/src/cdp/win-test-process.c [new file with mode: 0644]
regress/tests/cdp-client-tests.sh [new file with mode: 0755]
regress/tests/cdp-fd-plugin-test [new file with mode: 0755]
regress/tests/cdp-unittests [new file with mode: 0755]

diff --git a/regress/src/cdp/Makefile b/regress/src/cdp/Makefile
new file mode 100644 (file)
index 0000000..fa31915
--- /dev/null
@@ -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 (file)
index 0000000..d67e735
--- /dev/null
@@ -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 (file)
index 0000000..6ac762d
--- /dev/null
@@ -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 (file)
index 0000000..9fbe457
--- /dev/null
@@ -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 (file)
index 0000000..89f4d14
--- /dev/null
@@ -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 (file)
index 0000000..48bff84
--- /dev/null
@@ -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 <sys/file.h>
+#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 (file)
index 0000000..c088847
--- /dev/null
@@ -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 (executable)
index 0000000..4fc150b
--- /dev/null
@@ -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 (executable)
index 0000000..f7b5ab0
--- /dev/null
@@ -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 <<END_OF_DATA >$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 <<END_OF_DATA > $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 (executable)
index 0000000..b3f0315
--- /dev/null
@@ -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"