From 04a2acfe472a7e323d083e700e86c5576e33ba56 Mon Sep 17 00:00:00 2001
From: Sergey Yakubov <sergey.yakubov@desy.de>
Date: Thu, 21 Dec 2017 16:00:22 +0100
Subject: [PATCH] fix linux tests, link statically

---
 CMakeLists.txt                                |  9 ++-
 CMakeModules/testing_cpp.cmake                |  2 +-
 common/cpp/include/system_wrappers/io.h       |  4 +-
 .../cpp/include/system_wrappers/system_io.h   | 10 +--
 common/cpp/src/system_io.cpp                  | 45 ++++++------
 common/cpp/src/system_io_linux.cpp            | 38 +++++-----
 common/cpp/src/system_io_windows.cpp          | 73 +++++++++----------
 examples/worker/process_folder/CMakeLists.txt |  3 +
 .../read_files_in_folder/cleanup_linux.sh     |  2 +-
 .../read_files_in_folder/setup_linux.sh       |  4 +-
 .../worker/connect_multithread/CMakeLists.txt |  4 +
 .../content_multithread.cpp                   |  8 +-
 tests/worker/next_multithread/CMakeLists.txt  |  4 +
 .../api/cpp/unittests/test_folder_broker.cpp  |  2 +-
 14 files changed, 111 insertions(+), 97 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7322fe696..78e4d6160 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,13 @@
 cmake_minimum_required(VERSION 2.8)
+project(HIDRA2)
 set(CMAKE_CXX_STANDARD 11)
-set(CMAKE_CXX_FLAGS_DEBUG "/MTd")
+IF(WIN32)
+    set(CMAKE_CXX_FLAGS_DEBUG "/MTd")
+ELSEIF(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+    SET( CMAKE_EXE_LINKER_FLAGS  "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
+ENDIF(WIN32)
 
-project(HIDRA2)
+message(STATUS ${CMAKE_CXX_COMPILER_ID})
 
 option(BUILD_TESTS "Uses googletest to build tests" OFF)
 option(BUILD_INTEGRATION_TESTS "Include integration tests (CMAKE >3.7 is needed)" OFF)
diff --git a/CMakeModules/testing_cpp.cmake b/CMakeModules/testing_cpp.cmake
index 951ef8df6..d875ea84e 100644
--- a/CMakeModules/testing_cpp.cmake
+++ b/CMakeModules/testing_cpp.cmake
@@ -115,7 +115,7 @@ function(add_example_test testname)
         IF (WIN32)
             add_test(NAME test-${testname} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/check_windows.bat)
         ELSE()
-            add_test(NAME test-${testname} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/check_linux.sh)
+            add_test(NAME test-${testname} COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/check_linux.sh)
         ENDIF()
         set_tests_properties(test-${testname} PROPERTIES
                 LABELS "example;all"
diff --git a/common/cpp/include/system_wrappers/io.h b/common/cpp/include/system_wrappers/io.h
index a42cefa00..8370b7b9d 100644
--- a/common/cpp/include/system_wrappers/io.h
+++ b/common/cpp/include/system_wrappers/io.h
@@ -30,8 +30,8 @@ class IO {
 
     virtual int open(const char* __file, int __oflag) = 0;
     virtual int close(int __fd) = 0;
-    virtual int64_t read(int __fd, void *buf, size_t count) = 0;
-    virtual int64_t write(int __fd, const void *__buf, size_t __n) = 0;
+    virtual int64_t read(int __fd, void* buf, size_t count) = 0;
+    virtual int64_t write(int __fd, const void* __buf, size_t __n) = 0;
 
 // this is not standard function - to be implemented differently in windows and linux
     virtual std::vector<FileInfo> FilesInFolder(const std::string& folder, IOErrors* err) = 0;
diff --git a/common/cpp/include/system_wrappers/system_io.h b/common/cpp/include/system_wrappers/system_io.h
index 0a6647d2c..666fa7e53 100644
--- a/common/cpp/include/system_wrappers/system_io.h
+++ b/common/cpp/include/system_wrappers/system_io.h
@@ -10,13 +10,13 @@ class SystemIO final : public IO {
     FileData GetDataFromFile(const std::string& fname, uint64_t fsize, IOErrors* err) override;
     int open(const char* __file, int __oflag) override;
     int close(int __fd) override;
-    int64_t read(int __fd, void *buf, size_t count) override;
-    int64_t write(int __fd, const void *__buf, size_t __n) override;
+    int64_t read(int __fd, void* buf, size_t count) override;
+    int64_t write(int __fd, const void* __buf, size_t __n) override;
     std::vector<FileInfo> FilesInFolder(const std::string& folder, IOErrors* err) override;
- private:
+  private:
     void ReadWholeFile(int fd, uint8_t* array, uint64_t fsize, IOErrors* err);
-    void CollectFileInformationRecursivly(const std::string &path,
-                                          std::vector<FileInfo> &files, IOErrors *err);
+    void CollectFileInformationRecursivly(const std::string& path,
+                                          std::vector<FileInfo>& files, IOErrors* err);
 
 };
 }
diff --git a/common/cpp/src/system_io.cpp b/common/cpp/src/system_io.cpp
index 93292c5aa..66001ea13 100644
--- a/common/cpp/src/system_io.cpp
+++ b/common/cpp/src/system_io.cpp
@@ -12,19 +12,19 @@ namespace hidra2 {
 IOErrors IOErrorFromErrno() {
     IOErrors err;
     switch (errno) {
-        case 0:
-            err = IOErrors::kNoError;
-            break;
-        case ENOENT:
-        case ENOTDIR:
-            err = IOErrors::kFileNotFound;
-            break;
-        case EACCES:
-            err = IOErrors::kPermissionDenied;
-            break;
-        default:
-            err = IOErrors::kUnknownError;
-            break;
+    case 0:
+        err = IOErrors::kNoError;
+        break;
+    case ENOENT:
+    case ENOTDIR:
+        err = IOErrors::kFileNotFound;
+        break;
+    case EACCES:
+        err = IOErrors::kPermissionDenied;
+        break;
+    default:
+        err = IOErrors::kUnknownError;
+        break;
     }
     return err;
 }
@@ -51,9 +51,8 @@ FileData SystemIO::GetDataFromFile(const std::string& fname, uint64_t fsize, IOE
     uint8_t* data_array = nullptr;
     try {
         data_array = new uint8_t[fsize];
-    }
-    catch (...){
-        *err=IOErrors::kMemoryAllocationError;
+    } catch (...) {
+        *err = IOErrors::kMemoryAllocationError;
         return nullptr;
     }
 
@@ -73,21 +72,21 @@ FileData SystemIO::GetDataFromFile(const std::string& fname, uint64_t fsize, IOE
     return data;
 }
 
-void SortFileList(std::vector<FileInfo> &file_list) {
+void SortFileList(std::vector<FileInfo>& file_list) {
     std::sort(file_list.begin(), file_list.end(),
-              [](FileInfo const &a, FileInfo const &b) {
-                  return a.modify_date < b.modify_date;
-              });
+    [](FileInfo const & a, FileInfo const & b) {
+        return a.modify_date < b.modify_date;
+    });
 }
 
-void StripBasePath(const std::string &folder, std::vector<FileInfo> &file_list) {
+void StripBasePath(const std::string& folder, std::vector<FileInfo>& file_list) {
     auto n_erase = folder.size() + 1;
-    for (auto &file : file_list) {
+    for (auto& file : file_list) {
         file.relative_path.erase(0, n_erase);
     }
 }
 
-std::vector<FileInfo> SystemIO::FilesInFolder(const std::string &folder, IOErrors *err) {
+std::vector<FileInfo> SystemIO::FilesInFolder(const std::string& folder, IOErrors* err) {
     std::vector<FileInfo> files{};
     CollectFileInformationRecursivly(folder, files, err);
     if (*err != IOErrors::kNoError) {
diff --git a/common/cpp/src/system_io_linux.cpp b/common/cpp/src/system_io_linux.cpp
index 5ea9585a6..a932a90d3 100644
--- a/common/cpp/src/system_io_linux.cpp
+++ b/common/cpp/src/system_io_linux.cpp
@@ -6,6 +6,8 @@
 #include <sys/stat.h>
 #include <algorithm>
 
+#include <fcntl.h>
+
 #include <cerrno>
 #include <unistd.h>
 
@@ -15,37 +17,37 @@ using std::chrono::system_clock;
 
 namespace hidra2 {
 
-bool IsDirectory(const struct dirent *entity) {
+bool IsDirectory(const struct dirent* entity) {
     return entity->d_type == DT_DIR &&
-        strstr(entity->d_name, "..") == nullptr &&
-        strstr(entity->d_name, ".") == nullptr;
+           strstr(entity->d_name, "..") == nullptr &&
+           strstr(entity->d_name, ".") == nullptr;
 }
 
-void SetModifyDate(const struct stat &t_stat, FileInfo *file_info) {
+void SetModifyDate(const struct stat& t_stat, FileInfo* file_info) {
 #ifdef __APPLE__
 #define st_mtim st_mtimespec
 #endif
     std::chrono::nanoseconds d = std::chrono::nanoseconds {t_stat.st_mtim.tv_nsec} +
-        std::chrono::seconds{t_stat.st_mtim.tv_sec};
+                                 std::chrono::seconds{t_stat.st_mtim.tv_sec};
 #ifdef __APPLE__
 #undef st_mtim
 #endif
 
     file_info->modify_date = system_clock::time_point
-        {std::chrono::duration_cast<system_clock::duration>(d)};
+    {std::chrono::duration_cast<system_clock::duration>(d)};
 }
 
-void SetFileSize(const struct stat &t_stat, FileInfo *file_info) {
+void SetFileSize(const struct stat& t_stat, FileInfo* file_info) {
     file_info->size = t_stat.st_size;
 }
 
-void SetFileName(const string &path, const string &name, FileInfo *file_info) {
+void SetFileName(const string& path, const string& name, FileInfo* file_info) {
     file_info->relative_path = path;
     file_info->base_name = name;
 }
 
-struct stat FileStat(const string &fname, IOErrors *err) {
-    struct stat t_stat{};
+struct stat FileStat(const string& fname, IOErrors* err) {
+    struct stat t_stat {};
     int res = stat(fname.c_str(), &t_stat);
     if (res < 0) {
         *err = IOErrorFromErrno();
@@ -53,7 +55,7 @@ struct stat FileStat(const string &fname, IOErrors *err) {
     return t_stat;
 }
 
-FileInfo GetFileInfo(const string &path, const string &name, IOErrors *err) {
+FileInfo GetFileInfo(const string& path, const string& name, IOErrors* err) {
     FileInfo file_info;
 
     SetFileName(path, name, &file_info);
@@ -70,8 +72,8 @@ FileInfo GetFileInfo(const string &path, const string &name, IOErrors *err) {
     return file_info;
 }
 
-void ProcessFileEntity(const struct dirent *entity, const std::string &path,
-                       std::vector<FileInfo> &files, IOErrors *err) {
+void ProcessFileEntity(const struct dirent* entity, const std::string& path,
+                       std::vector<FileInfo>& files, IOErrors* err) {
 
     *err = IOErrors::kNoError;
     if (entity->d_type != DT_REG) {
@@ -86,15 +88,15 @@ void ProcessFileEntity(const struct dirent *entity, const std::string &path,
     files.push_back(file_info);
 }
 
-void SystemIO::CollectFileInformationRecursivly(const std::string &path,
-                                      std::vector<FileInfo> &files, IOErrors *err) {
+void SystemIO::CollectFileInformationRecursivly(const std::string& path,
+                                                std::vector<FileInfo>& files, IOErrors* err) {
     auto dir = opendir((path).c_str());
     if (dir == nullptr) {
         *err = IOErrorFromErrno();
         return;
     }
 
-    while (struct dirent *current_entity = readdir(dir)) {
+    while (struct dirent* current_entity = readdir(dir)) {
         if (IsDirectory(current_entity)) {
             CollectFileInformationRecursivly(path + "/" + current_entity->d_name,
                                              files, err);
@@ -111,11 +113,11 @@ void SystemIO::CollectFileInformationRecursivly(const std::string &path,
 }
 
 
-int64_t SystemIO::read(int __fd, void *buf, size_t count) {
+int64_t SystemIO::read(int __fd, void* buf, size_t count) {
     return (int64_t) ::read(__fd, buf, count);
 }
 
-int64_t SystemIO::write(int __fd, const void *__buf, size_t __n) {
+int64_t SystemIO::write(int __fd, const void* __buf, size_t __n) {
     return (int64_t) ::write(__fd, __buf, __n);
 }
 
diff --git a/common/cpp/src/system_io_windows.cpp b/common/cpp/src/system_io_windows.cpp
index f54310746..4b2d113ed 100644
--- a/common/cpp/src/system_io_windows.cpp
+++ b/common/cpp/src/system_io_windows.cpp
@@ -15,25 +15,24 @@ namespace hidra2 {
 IOErrors IOErrorFromGetLastError() {
     IOErrors err;
     switch (GetLastError()) {
-        case ERROR_SUCCESS :
-            err = IOErrors::kNoError;
-            break;
-        case ERROR_PATH_NOT_FOUND:
-        case ERROR_FILE_NOT_FOUND:
-            err = IOErrors::kFileNotFound;
-            break;
-        case ERROR_ACCESS_DENIED:
-            err = IOErrors::kPermissionDenied;
-            break;
-        default:
-            err = IOErrors::kUnknownError;
-            break;
+    case ERROR_SUCCESS :
+        err = IOErrors::kNoError;
+        break;
+    case ERROR_PATH_NOT_FOUND:
+    case ERROR_FILE_NOT_FOUND:
+        err = IOErrors::kFileNotFound;
+        break;
+    case ERROR_ACCESS_DENIED:
+        err = IOErrors::kPermissionDenied;
+        break;
+    default:
+        err = IOErrors::kUnknownError;
+        break;
     }
     return err;
 }
 
-std::chrono::system_clock::time_point FileTime2TimePoint(const FILETIME& ft,IOErrors* err)
-{
+std::chrono::system_clock::time_point FileTime2TimePoint(const FILETIME& ft, IOErrors* err) {
     SYSTEMTIME st = {0};
     if (!FileTimeToSystemTime(&ft, &st)) {
         *err = IOErrorFromGetLastError();
@@ -49,18 +48,18 @@ std::chrono::system_clock::time_point FileTime2TimePoint(const FILETIME& ft,IOEr
 
     auto tp = std::chrono::system_clock::from_time_t(secs);
     tp += ms;
-    *err=IOErrors::kNoError;
+    *err = IOErrors::kNoError;
     return tp;
 }
 
 bool IsDirectory(const WIN32_FIND_DATA f) {
     return (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
-        strstr(f.cFileName, "..") == nullptr &&
-        strstr(f.cFileName, ".") == nullptr;
+           strstr(f.cFileName, "..") == nullptr &&
+           strstr(f.cFileName, ".") == nullptr;
 }
 
-void ProcessFileEntity(const WIN32_FIND_DATA f, const std::string &path,
-                       std::vector<FileInfo> &files, IOErrors *err) {
+void ProcessFileEntity(const WIN32_FIND_DATA f, const std::string& path,
+                       std::vector<FileInfo>& files, IOErrors* err) {
 
     *err = IOErrors::kNoError;
     if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
@@ -68,33 +67,31 @@ void ProcessFileEntity(const WIN32_FIND_DATA f, const std::string &path,
     }
 
     FileInfo file_info;
-    file_info.modify_date=FileTime2TimePoint(f.ftLastWriteTime,err);
+    file_info.modify_date = FileTime2TimePoint(f.ftLastWriteTime, err);
     if (*err != IOErrors::kNoError) {
         return;
     }
 
-    file_info.base_name=f.cFileName;
-    file_info.relative_path=path;
+    file_info.base_name = f.cFileName;
+    file_info.relative_path = path;
     files.push_back(file_info);
 }
 
 
-void SystemIO::CollectFileInformationRecursivly(const std::string &path,
-                                      std::vector<FileInfo> &files, IOErrors *err) {
+void SystemIO::CollectFileInformationRecursivly(const std::string& path,
+                                                std::vector<FileInfo>& files, IOErrors* err) {
     WIN32_FIND_DATA find_data;
-    HANDLE handle = FindFirstFile((path+"\\*.*").c_str(), &find_data);
+    HANDLE handle = FindFirstFile((path + "\\*.*").c_str(), &find_data);
     if (handle == INVALID_HANDLE_VALUE) {
         *err = IOErrorFromGetLastError();
         return;
     }
 
-    do
-    {
-        if (IsDirectory(find_data))
-        {
-            CollectFileInformationRecursivly(path + "\\" + find_data.cFileName,files,err);
+    do {
+        if (IsDirectory(find_data)) {
+            CollectFileInformationRecursivly(path + "\\" + find_data.cFileName, files, err);
         } else {
-            ProcessFileEntity(find_data,path, files, err);
+            ProcessFileEntity(find_data, path, files, err);
         }
         if (*err != IOErrors::kNoError) {
             FindClose(handle);
@@ -102,25 +99,25 @@ void SystemIO::CollectFileInformationRecursivly(const std::string &path,
         }
     } while (FindNextFile(handle, &find_data));
 
-    if (FindClose(handle)){
-        *err=IOErrors ::kNoError;
-    }else{
+    if (FindClose(handle)) {
+        *err = IOErrors ::kNoError;
+    } else {
         *err = IOErrorFromGetLastError();
     }
 
 }
 
-int64_t SystemIO::read(int __fd, void *buf, size_t count) {
+int64_t SystemIO::read(int __fd, void* buf, size_t count) {
     return (int64_t) _read(__fd, buf, (unsigned int) count);
 }
 
-int64_t SystemIO::write(int __fd, const void *__buf, size_t __n) {
+int64_t SystemIO::write(int __fd, const void* __buf, size_t __n) {
     return (int64_t) _write(__fd, __buf, (unsigned int) __n);
 }
 
 int SystemIO::open(const char* __file, int __oflag) {
     int fd;
-    errno = _sopen_s(&fd,__file,__oflag,_SH_DENYNO,_S_IREAD | _S_IWRITE);
+    errno = _sopen_s(&fd, __file, __oflag, _SH_DENYNO, _S_IREAD | _S_IWRITE);
     return fd;
 }
 
diff --git a/examples/worker/process_folder/CMakeLists.txt b/examples/worker/process_folder/CMakeLists.txt
index 1d958e18d..9fe64b422 100644
--- a/examples/worker/process_folder/CMakeLists.txt
+++ b/examples/worker/process_folder/CMakeLists.txt
@@ -11,6 +11,9 @@ set(SOURCE_FILES process_folder.cpp)
 add_executable(${TARGET_NAME} ${SOURCE_FILES})
 target_link_libraries(${TARGET_NAME} common worker-api ${CMAKE_THREAD_LIBS_INIT})
 set_target_properties(${TARGET_NAME} PROPERTIES LINKER_LANGUAGE CXX)
+if (CMAKE_COMPILER_IS_GNUCXX)
+    set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS_DEBUG "--coverage")
+endif()
 #set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "-static-libstdc++")
 
 add_example_test("${TARGET_NAME}")
diff --git a/tests/system_io/read_files_in_folder/cleanup_linux.sh b/tests/system_io/read_files_in_folder/cleanup_linux.sh
index 2afed4ee2..c6183bf94 100644
--- a/tests/system_io/read_files_in_folder/cleanup_linux.sh
+++ b/tests/system_io/read_files_in_folder/cleanup_linux.sh
@@ -1,4 +1,4 @@
 #!/usr/bin/env bash
 
 rm -rf test
-rmdir test_noaccess
+rmdir test_noaccess1
diff --git a/tests/system_io/read_files_in_folder/setup_linux.sh b/tests/system_io/read_files_in_folder/setup_linux.sh
index 4516ede2b..0e159dace 100644
--- a/tests/system_io/read_files_in_folder/setup_linux.sh
+++ b/tests/system_io/read_files_in_folder/setup_linux.sh
@@ -9,6 +9,6 @@ touch test/subtest/subtest2/4
 sleep 0.1
 touch test/1
 
-mkdir test_noaccess
-chmod -rx test_noaccess
+mkdir test_noaccess1
+chmod -rx test_noaccess1
 
diff --git a/tests/worker/connect_multithread/CMakeLists.txt b/tests/worker/connect_multithread/CMakeLists.txt
index 5d7cc640f..8dc1b8ae7 100644
--- a/tests/worker/connect_multithread/CMakeLists.txt
+++ b/tests/worker/connect_multithread/CMakeLists.txt
@@ -8,6 +8,10 @@ set(SOURCE_FILES content_multithread.cpp)
 add_executable(${TARGET_NAME} ${SOURCE_FILES})
 target_link_libraries(${TARGET_NAME} test_common common worker-api ${CMAKE_THREAD_LIBS_INIT})
 set_target_properties(${TARGET_NAME} PROPERTIES LINKER_LANGUAGE CXX)
+if (CMAKE_COMPILER_IS_GNUCXX)
+    set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS_DEBUG "--coverage")
+endif()
+
 
 ################################
 # Testing
diff --git a/tests/worker/connect_multithread/content_multithread.cpp b/tests/worker/connect_multithread/content_multithread.cpp
index f89f2fc51..677fc65fc 100644
--- a/tests/worker/connect_multithread/content_multithread.cpp
+++ b/tests/worker/connect_multithread/content_multithread.cpp
@@ -10,12 +10,12 @@ using hidra2::WorkerErrorCode;
 
 void Assert(std::vector<WorkerErrorCode>& errors, int nthreads) {
     int count_ok = (int) std::count(std::begin(errors),
-                              std::end(errors),
-                              WorkerErrorCode::kOK);
+                                    std::end(errors),
+                                    WorkerErrorCode::kOK);
 
     int count_already_connected = (int) std::count(std::begin(errors),
-                                             std::end(errors),
-                                             WorkerErrorCode::kSourceAlreadyConnected);
+                                                   std::end(errors),
+                                                   WorkerErrorCode::kSourceAlreadyConnected);
     M_AssertEq(1, count_ok);
     M_AssertEq(nthreads - 1, count_already_connected);
 }
diff --git a/tests/worker/next_multithread/CMakeLists.txt b/tests/worker/next_multithread/CMakeLists.txt
index 48cab6201..13de6686b 100644
--- a/tests/worker/next_multithread/CMakeLists.txt
+++ b/tests/worker/next_multithread/CMakeLists.txt
@@ -8,6 +8,10 @@ set(SOURCE_FILES next_multithread.cpp)
 add_executable(${TARGET_NAME} ${SOURCE_FILES})
 target_link_libraries(${TARGET_NAME} test_common common worker-api  ${CMAKE_THREAD_LIBS_INIT})
 set_target_properties(${TARGET_NAME} PROPERTIES LINKER_LANGUAGE CXX)
+if (CMAKE_COMPILER_IS_GNUCXX)
+    set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS_DEBUG "--coverage")
+endif()
+
 
 ################################
 # Testing
diff --git a/worker/api/cpp/unittests/test_folder_broker.cpp b/worker/api/cpp/unittests/test_folder_broker.cpp
index ab3b2bd51..1942f1198 100644
--- a/worker/api/cpp/unittests/test_folder_broker.cpp
+++ b/worker/api/cpp/unittests/test_folder_broker.cpp
@@ -273,7 +273,7 @@ TEST_F(GetDataFromFileTests, GetNextReturnsErrorWhenCannotReadData) {
 
 TEST_F(GetDataFromFileTests, GetNextReturnsErrorWhenCannotAllocateData) {
     EXPECT_CALL(mock, GetDataFromFileProxy(_, _, _)).
-        WillOnce(DoAll(testing::SetArgPointee<2>(IOErrors::kMemoryAllocationError), testing::Return(nullptr)));
+    WillOnce(DoAll(testing::SetArgPointee<2>(IOErrors::kMemoryAllocationError), testing::Return(nullptr)));
 
     auto err = data_broker->GetNext(&fi, &data);
 
-- 
GitLab