From da98870ec3c4bf1b434c1f312356808cff3f8b59 Mon Sep 17 00:00:00 2001
From: Sergey Yakubov <sergey.yakubov@desy.de>
Date: Fri, 8 Dec 2017 17:28:42 +0100
Subject: [PATCH] added OpenFile and tests

---
 CMakeModules/testing_cpp.cmake                |   2 +-
 common/cpp/include/common/file_info.h         |   2 +
 common/cpp/include/system_wrappers/io.h       |   7 +-
 .../cpp/include/system_wrappers/system_io.h   |   1 +
 common/cpp/src/system_io.cpp                  |   7 +
 common/cpp/src/system_io_linux.cpp            |   5 +-
 tests/system_io/read_folder_content.cpp       |   2 +-
 worker/api/cpp/include/worker/data_broker.h   |  16 ++-
 worker/api/cpp/src/data_broker.cpp            |   6 +-
 worker/api/cpp/src/folder_data_broker.cpp     |  59 +++++++-
 worker/api/cpp/src/folder_data_broker.h       |   6 +
 .../api/cpp/unittests/test_folder_broker.cpp  | 132 ++++++++++++++++--
 worker/api/cpp/unittests/test_worker_api.cpp  |   6 +-
 13 files changed, 224 insertions(+), 27 deletions(-)

diff --git a/CMakeModules/testing_cpp.cmake b/CMakeModules/testing_cpp.cmake
index ab96564a5..7f20142f7 100644
--- a/CMakeModules/testing_cpp.cmake
+++ b/CMakeModules/testing_cpp.cmake
@@ -10,7 +10,7 @@ function(gtest target test_source_files test_libraries)
     if (BUILD_TESTS)
         include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
         add_executable(test-${target} ${test_source_files})
-        target_link_libraries(test-${target} gtest gtest_main ${CMAKE_THREAD_LIBS_INIT})
+        target_link_libraries(test-${target} gtest gmock gtest_main ${CMAKE_THREAD_LIBS_INIT})
         if (NOT ${test_libraries} STREQUAL "")
             target_link_libraries(test-${target} ${test_libraries})
         endif ()
diff --git a/common/cpp/include/common/file_info.h b/common/cpp/include/common/file_info.h
index e75678442..fe123c60b 100644
--- a/common/cpp/include/common/file_info.h
+++ b/common/cpp/include/common/file_info.h
@@ -1,6 +1,8 @@
 #ifndef HIDRA2_FILE_INFO_H
 #define HIDRA2_FILE_INFO_H
 
+#include <chrono>
+
 namespace hidra2 {
 
 struct FileInfo {
diff --git a/common/cpp/include/system_wrappers/io.h b/common/cpp/include/system_wrappers/io.h
index ead7c2739..b4bdcd830 100644
--- a/common/cpp/include/system_wrappers/io.h
+++ b/common/cpp/include/system_wrappers/io.h
@@ -12,13 +12,18 @@ namespace hidra2 {
 
 enum class IOErrors {
     NO_ERROR,
-    FOLDER_NOT_FOUND,
+    FILE_NOT_FOUND,
     PERMISSIONS_DENIED,
     UNKWOWN_ERROR
 };
 
+IOErrors IOErrorFromErrno();
+
 class IO {
   public:
+
+    virtual int OpenFileToRead(const std::string& fname, IOErrors* err) = 0;
+
     virtual int open(const char* __file, int __oflag) = 0;
     virtual int close(int __fd) = 0;
     virtual ssize_t read(int __fd, void* buf, size_t count) = 0;
diff --git a/common/cpp/include/system_wrappers/system_io.h b/common/cpp/include/system_wrappers/system_io.h
index def69d206..13b1ea53c 100644
--- a/common/cpp/include/system_wrappers/system_io.h
+++ b/common/cpp/include/system_wrappers/system_io.h
@@ -7,6 +7,7 @@ namespace hidra2 {
 
 class SystemIO final : public IO {
   public:
+    int OpenFileToRead(const std::string& fname, IOErrors* err);
     int open(const char* __file, int __oflag);
     int close(int __fd);
     ssize_t read(int __fd, void* buf, size_t count);
diff --git a/common/cpp/src/system_io.cpp b/common/cpp/src/system_io.cpp
index 34a78141e..df63bab04 100644
--- a/common/cpp/src/system_io.cpp
+++ b/common/cpp/src/system_io.cpp
@@ -2,6 +2,13 @@
 #include <unistd.h>
 #include <system_wrappers/system_io.h>
 
+int hidra2::SystemIO::OpenFileToRead(const std::string& fname, IOErrors* err) {
+    int fd = ::open(fname.c_str(), O_RDONLY);
+    *err = IOErrorFromErrno();
+    return fd;
+}
+
+
 int hidra2::SystemIO::open(const char* __file, int __oflag) {
     return ::open(__file, __oflag);
 }
diff --git a/common/cpp/src/system_io_linux.cpp b/common/cpp/src/system_io_linux.cpp
index e74694106..19e517b6d 100644
--- a/common/cpp/src/system_io_linux.cpp
+++ b/common/cpp/src/system_io_linux.cpp
@@ -20,7 +20,7 @@ IOErrors IOErrorFromErrno() {
         break;
     case ENOENT:
     case ENOTDIR:
-        err = IOErrors::FOLDER_NOT_FOUND;
+        err = IOErrors::FILE_NOT_FOUND;
         break;
     case EACCES:
         err = IOErrors::PERMISSIONS_DENIED;
@@ -32,6 +32,7 @@ IOErrors IOErrorFromErrno() {
     return err;
 }
 
+
 bool IsDirectory(const struct dirent* entity) {
     return entity->d_type == DT_DIR &&
            strstr(entity->d_name, "..") == nullptr &&
@@ -40,7 +41,7 @@ bool IsDirectory(const struct dirent* entity) {
 
 system_clock::time_point GetTimePointFromFile(const string& fname, IOErrors* err) {
 
-    struct stat t_stat{};
+    struct stat t_stat {};
     int res = stat(fname.c_str(), &t_stat);
     if (res < 0) {
         *err = IOErrorFromErrno();
diff --git a/tests/system_io/read_folder_content.cpp b/tests/system_io/read_folder_content.cpp
index 8391c73bc..d924c8aa4 100644
--- a/tests/system_io/read_folder_content.cpp
+++ b/tests/system_io/read_folder_content.cpp
@@ -30,7 +30,7 @@ int main(int argc, char* argv[]) {
     std::string result;
 
     switch (err) {
-    case IOErrors::FOLDER_NOT_FOUND:
+    case IOErrors::FILE_NOT_FOUND:
         result = "notfound";
         break;
     case IOErrors::NO_ERROR:
diff --git a/worker/api/cpp/include/worker/data_broker.h b/worker/api/cpp/include/worker/data_broker.h
index df931e4ac..216cfe8e9 100644
--- a/worker/api/cpp/include/worker/data_broker.h
+++ b/worker/api/cpp/include/worker/data_broker.h
@@ -4,20 +4,30 @@
 #include <memory>
 #include <string>
 #include <iostream>
+#include <common/file_info.h>
+#include <vector>
 
 namespace hidra2 {
 
 enum class WorkerErrorCode {
-    ERR__NO_ERROR,
-    ERR__MEMORY_ERROR,
-    ERR__EMPTY_DATASOURCE,
+    OK,
+    MEMORY_ERROR,
+    EMPTY_DATASOURCE,
     SOURCE_NOT_FOUND,
+    SOURCE_NOT_CONNECTED,
+    SOURCE_ALREADY_CONNECTED,
+    PERMISSIONS_DENIED,
+    NO_DATA,
+    WRONG_INPUT,
     UNKNOWN_IO_ERROR
 };
 
+typedef std::vector<char> FileData;
+
 class DataBroker {
   public:
     virtual WorkerErrorCode Connect() = 0;
+    virtual WorkerErrorCode GetNext(FileInfo* info, FileData* data) = 0;
     virtual ~DataBroker() = default; // needed for unique_ptr to delete itself
 };
 
diff --git a/worker/api/cpp/src/data_broker.cpp b/worker/api/cpp/src/data_broker.cpp
index cdcd96684..39f158393 100644
--- a/worker/api/cpp/src/data_broker.cpp
+++ b/worker/api/cpp/src/data_broker.cpp
@@ -7,16 +7,16 @@ std::unique_ptr<DataBroker> DataBrokerFactory::Create(const std::string& source_
         WorkerErrorCode* return_code) noexcept {
 
     if (source_name.empty()) {
-        *return_code = WorkerErrorCode::ERR__EMPTY_DATASOURCE;
+        *return_code = WorkerErrorCode::EMPTY_DATASOURCE;
         return nullptr;
     }
 
     std::unique_ptr<DataBroker> p = nullptr;
     try {
         p.reset(new FolderDataBroker(source_name));
-        *return_code = WorkerErrorCode::ERR__NO_ERROR;
+        *return_code = WorkerErrorCode::OK;
     } catch (...) {         // we do not test this part
-        *return_code = WorkerErrorCode::ERR__MEMORY_ERROR;
+        *return_code = WorkerErrorCode::MEMORY_ERROR;
     }
 
     return p;
diff --git a/worker/api/cpp/src/folder_data_broker.cpp b/worker/api/cpp/src/folder_data_broker.cpp
index e464ac107..85be42d47 100644
--- a/worker/api/cpp/src/folder_data_broker.cpp
+++ b/worker/api/cpp/src/folder_data_broker.cpp
@@ -1,7 +1,5 @@
 #include "folder_data_broker.h"
 
-#include <map>
-
 #include "system_wrappers/system_io.h"
 
 namespace hidra2 {
@@ -10,11 +8,14 @@ WorkerErrorCode MapIOError(IOErrors io_err) {
     WorkerErrorCode err;
     switch (io_err) { // we do not use map due to performance reasons
     case IOErrors::NO_ERROR:
-        err = WorkerErrorCode::ERR__NO_ERROR;
+        err = WorkerErrorCode::OK;
         break;
-    case IOErrors::FOLDER_NOT_FOUND:
+    case IOErrors::FILE_NOT_FOUND:
         err = WorkerErrorCode::SOURCE_NOT_FOUND;
         break;
+    case IOErrors::PERMISSIONS_DENIED:
+        err = WorkerErrorCode::PERMISSIONS_DENIED;
+        break;
     default:
         err = WorkerErrorCode::UNKNOWN_IO_ERROR;
         break;
@@ -24,15 +25,61 @@ WorkerErrorCode MapIOError(IOErrors io_err) {
 }
 
 
-FolderDataBroker::FolderDataBroker(const std::string& source_name) : base_path_{source_name},
-    io__{new hidra2::SystemIO} {
+FolderDataBroker::FolderDataBroker(const std::string& source_name) :
+    base_path_{source_name}, io__{new hidra2::SystemIO}, is_connected_{false},
+current_file_{0} {
 }
 
 WorkerErrorCode FolderDataBroker::Connect() {
     IOErrors io_err;
+    if (is_connected_) {
+        return WorkerErrorCode::SOURCE_ALREADY_CONNECTED;
+    }
+
     filelist_ = io__->FilesInFolder(base_path_, &io_err);
+
+    if (io_err == IOErrors::NO_ERROR) {
+        is_connected_ = true;
+    }
     return MapIOError(io_err);
 }
 
+WorkerErrorCode FolderDataBroker::CheckCanGetData(FileInfo* info, FileData* data) {
+    if (!is_connected_) {
+        return WorkerErrorCode::SOURCE_NOT_CONNECTED;
+    }
+
+    if (info == nullptr && data == nullptr) {
+        return WorkerErrorCode::WRONG_INPUT;
+    }
+
+    if (current_file_ >= filelist_.size()) {
+        return WorkerErrorCode::NO_DATA;
+    }
+    return WorkerErrorCode::OK;
+}
+
+
+WorkerErrorCode FolderDataBroker::GetNext(FileInfo* info, FileData* data) {
+    auto err = CheckCanGetData(info, data);
+    if (err != WorkerErrorCode::OK) {
+        return err;
+    }
+
+    *info = filelist_[current_file_];
+    current_file_++;
+
+    if (data == nullptr) {
+        return WorkerErrorCode::OK;
+    }
+
+    IOErrors ioerr;
+    auto fd = io__->OpenFileToRead(base_path_+"/"+info->relative_path +
+        (info->relative_path.empty()?"":"/")+
+        info->base_name, &ioerr);
+
+    return MapIOError(ioerr);
+}
+
 
 }
\ No newline at end of file
diff --git a/worker/api/cpp/src/folder_data_broker.h b/worker/api/cpp/src/folder_data_broker.h
index 564751158..3de03b058 100644
--- a/worker/api/cpp/src/folder_data_broker.h
+++ b/worker/api/cpp/src/folder_data_broker.h
@@ -14,11 +14,17 @@ class FolderDataBroker final : public hidra2::DataBroker {
   public:
     explicit FolderDataBroker(const std::string& source_name);
     WorkerErrorCode Connect() override;
+    WorkerErrorCode GetNext(FileInfo* info, FileData* data) override;
+
     std::unique_ptr<hidra2::IO> io__; // modified in testings to mock system calls,otherwise do not touch
 
   private:
+    bool is_connected_;
+    int current_file_;
     std::string base_path_;
     std::vector<FileInfo>  filelist_;
+    WorkerErrorCode CheckCanGetData(FileInfo* info, FileData* data);
+
 };
 
 }
diff --git a/worker/api/cpp/unittests/test_folder_broker.cpp b/worker/api/cpp/unittests/test_folder_broker.cpp
index a8b891b1e..8bec87e88 100644
--- a/worker/api/cpp/unittests/test_folder_broker.cpp
+++ b/worker/api/cpp/unittests/test_folder_broker.cpp
@@ -1,4 +1,6 @@
 #include <gmock/gmock.h>
+#include "gtest/gtest.h"
+using ::testing::AtLeast;
 
 #include "worker/data_broker.h"
 #include "system_wrappers/io.h"
@@ -12,10 +14,15 @@ using hidra2::WorkerErrorCode;
 using hidra2::IO;
 using hidra2::IOErrors;
 using hidra2::FileInfo;
+using hidra2::FileData;
+
 
 using ::testing::Eq;
 using ::testing::Ne;
 using ::testing::Test;
+using ::testing::_;
+using ::testing::Mock;
+
 
 namespace {
 
@@ -29,9 +36,15 @@ TEST(FolderDataBroker, SetCorrectIO) {
 
 class FakeIO: public IO {
   public:
+    int OpenFileToRead(const std::string& fname, IOErrors* err)  {
+        *err = IOErrors::NO_ERROR;
+        return 1;
+    };
+
     int open(const char* __file, int __oflag) {
         return 0;
     };
+
     int close(int __fd) {
         return 0;
     };
@@ -43,14 +56,23 @@ class FakeIO: public IO {
     };
     std::vector<FileInfo> FilesInFolder(const std::string& folder, IOErrors* err) {
         *err = IOErrors::NO_ERROR;
-        return {};
+        std::vector<FileInfo> file_infos;
+        FileInfo fi;
+        fi.base_name = "1";
+        file_infos.push_back(fi);
+        fi.base_name = "2";
+        file_infos.push_back(fi);
+        fi.base_name = "3";
+        file_infos.push_back(fi);
+
+        return file_infos;
     }
 };
 
 class IOFolderNotFound: public FakeIO {
   public:
     std::vector<FileInfo> FilesInFolder(const std::string& folder, IOErrors* err) {
-        *err = IOErrors::FOLDER_NOT_FOUND;
+        *err = IOErrors::FILE_NOT_FOUND;
         return {};
     }
 };
@@ -63,6 +85,24 @@ class IOFodlerUnknownError: public FakeIO {
     }
 };
 
+class IOEmptyFodler: public FakeIO {
+  public:
+    std::vector<FileInfo> FilesInFolder(const std::string& folder, IOErrors* err) {
+        *err = IOErrors::NO_ERROR;
+        return {};
+    }
+};
+
+class IOCannotOpenFile: public FakeIO {
+  public:
+    int OpenFileToRead(const std::string& fname, IOErrors* err)  {
+        *err = IOErrors::PERMISSIONS_DENIED;
+        return 1;
+    };
+};
+
+
+
 class FolderDataBrokerTests : public Test {
   public:
     std::unique_ptr<FolderDataBroker> data_broker;
@@ -77,9 +117,18 @@ class FolderDataBrokerTests : public Test {
 TEST_F(FolderDataBrokerTests, CanConnect) {
     auto return_code = data_broker->Connect();
 
-    ASSERT_THAT(return_code, Eq(WorkerErrorCode::ERR__NO_ERROR));
+    ASSERT_THAT(return_code, Eq(WorkerErrorCode::OK));
+}
+
+TEST_F(FolderDataBrokerTests, CannotConnectTwice) {
+    data_broker->Connect();
+
+    auto return_code = data_broker->Connect();
+
+    ASSERT_THAT(return_code, Eq(WorkerErrorCode::SOURCE_ALREADY_CONNECTED));
 }
 
+
 TEST_F(FolderDataBrokerTests, CannotConnectWhenNoFolder) {
     data_broker->io__ = std::unique_ptr<IO> {new IOFolderNotFound()};
 
@@ -96,13 +145,80 @@ TEST_F(FolderDataBrokerTests, ConnectReturnsUnknownIOError) {
     ASSERT_THAT(return_code, Eq(WorkerErrorCode::UNKNOWN_IO_ERROR));
 }
 
-/*TEST_F(FolderDataBrokerTests, GetNextWithouConnectReturnsError) {
-    WorkerErrorCode err;
-
-    auto err=data_broker->GetNext(nullptr,nullptr);
+TEST_F(FolderDataBrokerTests, GetNextWithoutConnectReturnsError) {
+    auto err = data_broker->GetNext(nullptr, nullptr);
 
     ASSERT_THAT(err, Eq(WorkerErrorCode::SOURCE_NOT_CONNECTED));
 }
 
-*/
+TEST_F(FolderDataBrokerTests, GetNextWithNullPointersReturnsError) {
+    data_broker->Connect();
+
+    auto err = data_broker->GetNext(nullptr, nullptr);
+
+    ASSERT_THAT(err, Eq(WorkerErrorCode::WRONG_INPUT));
+}
+
+
+TEST_F(FolderDataBrokerTests, GetNextReturnsFileInfo) {
+    data_broker->Connect();
+    FileInfo fi;
+
+    auto err = data_broker->GetNext(&fi, nullptr);
+
+    ASSERT_THAT(err, Eq(WorkerErrorCode::OK));
+    ASSERT_THAT(fi.base_name, Eq("1"));
+}
+
+TEST_F(FolderDataBrokerTests, SecondNextReturnsAnotherFileInfo) {
+    data_broker->Connect();
+    FileInfo fi;
+    data_broker->GetNext(&fi, nullptr);
+
+    auto err = data_broker->GetNext(&fi, nullptr);
+
+    ASSERT_THAT(err, Eq(WorkerErrorCode::OK));
+    ASSERT_THAT(fi.base_name, Eq("2"));
+}
+
+TEST_F(FolderDataBrokerTests, GetNextFromEmptyFolderReturnsError) {
+    data_broker->io__ = std::unique_ptr<IO> {new IOEmptyFodler()};
+    data_broker->Connect();
+    FileInfo fi;
+
+    auto err = data_broker->GetNext(&fi, nullptr);
+    ASSERT_THAT(err, Eq(WorkerErrorCode::NO_DATA));
+}
+
+
+TEST_F(FolderDataBrokerTests, GetNextReturnsErrorWhenFilePermissionsDenied) {
+    data_broker->io__ = std::unique_ptr<IO> {new IOCannotOpenFile()};
+    data_broker->Connect();
+    FileInfo fi;
+    FileData data;
+
+    auto err = data_broker->GetNext(&fi, &data);
+    ASSERT_THAT(err, Eq(WorkerErrorCode::PERMISSIONS_DENIED));
+}
+
+class OpenFileMock : public FakeIO {
+  public:
+    MOCK_METHOD2(OpenFileToRead, int(const std::string&, IOErrors*));
+};
+
+TEST_F(FolderDataBrokerTests, GetNextCallsOpenFileWithFileName) {
+    OpenFileMock* mock=new OpenFileMock;
+    data_broker->io__.reset(mock);
+    data_broker->Connect();
+    FileInfo fi;
+    FileData data;
+
+    EXPECT_CALL(*mock, OpenFileToRead("/path/to/file/1",_));
+    data_broker->GetNext(&fi, &data);
+
+    Mock::AllowLeak(mock);
+
+}
+
+
 }
diff --git a/worker/api/cpp/unittests/test_worker_api.cpp b/worker/api/cpp/unittests/test_worker_api.cpp
index 681a99462..ea35b54ac 100644
--- a/worker/api/cpp/unittests/test_worker_api.cpp
+++ b/worker/api/cpp/unittests/test_worker_api.cpp
@@ -12,6 +12,8 @@ using ::testing::Eq;
 using ::testing::Ne;
 using ::testing::Test;
 
+
+
 namespace {
 
 TEST(DataBrokerFactoryTests, CreateFolderDataSource) {
@@ -19,7 +21,7 @@ TEST(DataBrokerFactoryTests, CreateFolderDataSource) {
 
     auto data_broker = DataBrokerFactory::Create("path/to/file", &return_code);
 
-    ASSERT_THAT(return_code, Eq(WorkerErrorCode::ERR__NO_ERROR));
+    ASSERT_THAT(return_code, Eq(WorkerErrorCode::OK));
     ASSERT_THAT(dynamic_cast<FolderDataBroker*>(data_broker.get()), Ne(nullptr));
 }
 
@@ -28,7 +30,7 @@ TEST(DataBrokerFactoryTests, FailCreateDataSourceWithEmptySource) {
 
     auto data_broker = DataBrokerFactory::Create("", &return_code);
 
-    ASSERT_THAT(return_code, Eq(WorkerErrorCode::ERR__EMPTY_DATASOURCE));
+    ASSERT_THAT(return_code, Eq(WorkerErrorCode::EMPTY_DATASOURCE));
     ASSERT_THAT(data_broker.release(), Eq(nullptr));
 }
 
-- 
GitLab