add dynamic library interface to enable testing
Add interface defining the methods for dynamic linking to enable
testing.
Change-Id: If4d090d3cedc019b426435a1f651191803bfc1a9
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/Makefile.am b/Makefile.am
index c0cbffb..08f5235 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,7 +8,9 @@
manager.cpp \
process.cpp \
crc.cpp \
- utils.cpp
+ utils.cpp \
+ internal/sys.cpp \
+ fs.cpp
libblobcmds_la_LDFLAGS = \
$(SYSTEMD_LIBS) \
diff --git a/fs.cpp b/fs.cpp
new file mode 100644
index 0000000..0c20d83
--- /dev/null
+++ b/fs.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fs.hpp"
+
+#include <experimental/filesystem>
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+namespace fs = std::experimental::filesystem;
+
+std::vector<std::string> getLibraryList(const std::string& path,
+ PathMatcher check)
+{
+ std::vector<std::string> output;
+
+ for (const auto& p : fs::recursive_directory_iterator(path))
+ {
+ auto ps = p.path().string();
+
+ if (check(ps))
+ {
+ output.push_back(ps);
+ }
+ }
+
+ /* Algorithm ends up being two-pass, but expectation is a list under 10. */
+ return output;
+}
+
+} // namespace blobs
diff --git a/fs.hpp b/fs.hpp
new file mode 100644
index 0000000..2fb574e
--- /dev/null
+++ b/fs.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+using PathMatcher = std::function<bool(const std::string& filename)>;
+
+/**
+ * Returns a list of library paths. Checks against match method.
+ *
+ * TODO: Can be dropped if we implement a clean fs wrapper for test injection.
+ *
+ * @param[in] path - the path to search
+ * @param[in] check - the function to call to check the path
+ * @return a list of paths that match the criteria
+ */
+std::vector<std::string> getLibraryList(const std::string& path,
+ PathMatcher check);
+
+} // namespace blobs
diff --git a/internal/sys.cpp b/internal/sys.cpp
new file mode 100644
index 0000000..2aec78b
--- /dev/null
+++ b/internal/sys.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "sys.hpp"
+
+#include <dlfcn.h>
+
+namespace blobs
+{
+
+namespace internal
+{
+
+const char* DlSysImpl::dlerror() const
+{
+ return ::dlerror();
+}
+
+void* DlSysImpl::dlopen(const char* filename, int flags) const
+{
+ return ::dlopen(filename, flags);
+}
+
+void* DlSysImpl::dlsym(void* handle, const char* symbol) const
+{
+ return ::dlsym(handle, symbol);
+}
+
+DlSysImpl dlsys_impl;
+
+} // namespace internal
+} // namespace blobs
diff --git a/internal/sys.hpp b/internal/sys.hpp
new file mode 100644
index 0000000..e0a02bf
--- /dev/null
+++ b/internal/sys.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+namespace blobs
+{
+
+namespace internal
+{
+/**
+ * Interface to the dynamic library loader.
+ */
+class DlSysInterface
+{
+ public:
+ virtual ~DlSysInterface() = default;
+
+ /**
+ * obtain error diagnostic for functions in the dlopen API
+ *
+ * @return the error details
+ */
+ virtual const char* dlerror() const = 0;
+
+ /**
+ * open a shared object
+ *
+ * @param[in] filename - the path to the shared object or the filename to
+ * for searching
+ * @param[in] flags - the flags
+ * @return a handle to the shared object (null on failure)
+ */
+ virtual void* dlopen(const char* filename, int flags) const = 0;
+
+ /**
+ * obtain address of a symbol in a shared object or executable
+ *
+ * @param[in] handle - pointer to shared object
+ * @param[in] symbol - name of the symbol to find
+ * @return the address of the symbol if found (null otherwise)
+ */
+ virtual void* dlsym(void* handle, const char* symbol) const = 0;
+};
+
+class DlSysImpl : public DlSysInterface
+{
+ public:
+ const char* dlerror() const override;
+ void* dlopen(const char* filename, int flags) const override;
+ void* dlsym(void* handle, const char* symbol) const override;
+};
+
+extern DlSysImpl dlsys_impl;
+
+} // namespace internal
+} // namespace blobs
diff --git a/main.cpp b/main.cpp
index 5fdb53d..ee236d9 100644
--- a/main.cpp
+++ b/main.cpp
@@ -78,7 +78,7 @@
/* Install handlers. */
try
{
- loadLibraries(expectedHandlerPath);
+ loadLibraries(getBlobManager(), expectedHandlerPath);
}
catch (const std::exception& e)
{
diff --git a/test/Makefile.am b/test/Makefile.am
index 5e0cdb0..0d36509 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -35,7 +35,9 @@
manager_read_unittest \
manager_writemeta_unittest \
process_unittest \
- crc_unittest
+ crc_unittest \
+ utils_unittest
+
TESTS = $(check_PROGRAMS)
ipmi_unittest_SOURCES = ipmi_unittest.cpp
@@ -116,3 +118,6 @@
crc_unittest_SOURCES = crc_unittest.cpp
crc_unittest_LDADD = $(top_builddir)/crc.o
+
+utils_unittest_SOURCES = utils_unittest.cpp
+utils_unittest_LDADD = $(top_builddir)/utils.o $(PHOSPHOR_LOGGING_LIBS)
diff --git a/test/dlsys_mock.hpp b/test/dlsys_mock.hpp
new file mode 100644
index 0000000..795b40e
--- /dev/null
+++ b/test/dlsys_mock.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "internal/sys.hpp"
+
+#include <gmock/gmock.h>
+
+namespace blobs
+{
+namespace internal
+{
+
+class InternalDlSysMock : public DlSysInterface
+{
+ public:
+ virtual ~InternalDlSysMock() = default;
+
+ MOCK_CONST_METHOD0(dlerror, const char*());
+ MOCK_CONST_METHOD2(dlopen, void*(const char*, int));
+ MOCK_CONST_METHOD2(dlsym, void*(void*, const char*));
+};
+
+} // namespace internal
+} // namespace blobs
diff --git a/test/utils_unittest.cpp b/test/utils_unittest.cpp
new file mode 100644
index 0000000..8a0cdca
--- /dev/null
+++ b/test/utils_unittest.cpp
@@ -0,0 +1,96 @@
+#include "dlsys_mock.hpp"
+#include "fs.hpp"
+#include "utils.hpp"
+
+#include <blobs-ipmid/test/blob_mock.hpp>
+#include <blobs-ipmid/test/manager_mock.hpp>
+#include <experimental/filesystem>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace fs = std::experimental::filesystem;
+
+namespace blobs
+{
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::StrictMock;
+
+std::vector<std::string>* returnList = nullptr;
+
+std::vector<std::string> getLibraryList(const std::string& path,
+ PathMatcher check)
+{
+ return (returnList) ? *returnList : std::vector<std::string>();
+}
+
+std::unique_ptr<GenericBlobInterface> factoryReturn;
+
+std::unique_ptr<GenericBlobInterface> fakeFactory()
+{
+ return std::move(factoryReturn);
+}
+
+TEST(UtilLoadLibraryTest, NoFilesFound)
+{
+ /* Verify nothing special happens when there are no files found. */
+
+ StrictMock<internal::InternalDlSysMock> dlsys;
+ StrictMock<ManagerMock> manager;
+
+ loadLibraries(&manager, "", &dlsys);
+}
+
+TEST(UtilLoadLibraryTest, OneFileFoundIsLibrary)
+{
+ /* Verify if it finds a library, and everything works, it'll regsiter it.
+ */
+
+ std::vector<std::string> files = {"this.fake"};
+ returnList = &files;
+
+ StrictMock<internal::InternalDlSysMock> dlsys;
+ StrictMock<ManagerMock> manager;
+ void* handle = reinterpret_cast<void*>(0x01);
+ auto blobMock = std::make_unique<BlobMock>();
+
+ factoryReturn = std::move(blobMock);
+
+ EXPECT_CALL(dlsys, dlopen(_, _)).WillOnce(Return(handle));
+
+ EXPECT_CALL(dlsys, dlerror()).Times(2).WillRepeatedly(Return(nullptr));
+
+ EXPECT_CALL(dlsys, dlsym(handle, StrEq("createHandler")))
+ .WillOnce(Return(reinterpret_cast<void*>(fakeFactory)));
+
+ EXPECT_CALL(manager, registerHandler(_));
+
+ loadLibraries(&manager, "", &dlsys);
+}
+
+TEST(UtilLibraryMatchTest, TestAll)
+{
+ struct LibraryMatch
+ {
+ std::string name;
+ bool expectation;
+ };
+
+ std::vector<LibraryMatch> tests = {
+ {"libblobcmds.0.0.1", false}, {"libblobcmds.0.0", false},
+ {"libblobcmds.0", false}, {"libblobcmds.10", false},
+ {"libblobcmds.a", false}, {"libcmds.so.so.0", true},
+ {"libcmds.so.0", true}, {"libcmds.so.0.0", false},
+ {"libcmds.so.0.0.10", false}, {"libblobs.so.1000", true}};
+
+ for (const auto& test : tests)
+ {
+ EXPECT_EQ(test.expectation, matchBlobHandler(test.name));
+ }
+}
+
+} // namespace blobs
diff --git a/utils.cpp b/utils.cpp
index 1e1ef91..24166ca 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -16,60 +16,55 @@
#include "utils.hpp"
+#include "fs.hpp"
+
#include <dlfcn.h>
#include <blobs-ipmid/manager.hpp>
-#include <experimental/filesystem>
#include <memory>
#include <phosphor-logging/log.hpp>
#include <regex>
+#include <string>
+#include <vector>
namespace blobs
{
-namespace fs = std::experimental::filesystem;
using namespace phosphor::logging;
-using HandlerFactory = std::unique_ptr<GenericBlobInterface> (*)();
+bool matchBlobHandler(const std::string& filename)
+{
+ return std::regex_match(filename, std::regex(".+\\.so\\.\\d+$"));
+}
-void loadLibraries(const std::string& path)
+void loadLibraries(ManagerInterface* manager, const std::string& path,
+ const internal::DlSysInterface* sys)
{
void* libHandle = NULL;
HandlerFactory factory;
- auto* manager = getBlobManager();
- for (const auto& p : fs::recursive_directory_iterator(path))
+ std::vector<std::string> libs = getLibraryList(path, matchBlobHandler);
+
+ for (const auto& p : libs)
{
- auto ps = p.path().string();
-
- /* The bitbake recipe symlinks the library lib*.so.? into the folder
- * only, and not the other names, .so, .so.?.?, .so.?.?.?
- *
- * Therefore only care if it's lib*.so.?
- */
- if (!std::regex_match(ps, std::regex(".+\\.so\\.\\d+$")))
- {
- continue;
- }
-
- libHandle = dlopen(ps.c_str(), RTLD_NOW | RTLD_GLOBAL);
+ libHandle = sys->dlopen(p.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (!libHandle)
{
- log<level::ERR>("ERROR opening", entry("HANDLER=%s", ps.c_str()),
- entry("ERROR=%s", dlerror()));
+ log<level::ERR>("ERROR opening", entry("HANDLER=%s", p.c_str()),
+ entry("ERROR=%s", sys->dlerror()));
continue;
}
- dlerror(); /* Clear any previous error. */
+ sys->dlerror(); /* Clear any previous error. */
- factory =
- reinterpret_cast<HandlerFactory>(dlsym(libHandle, "createHandler"));
+ factory = reinterpret_cast<HandlerFactory>(
+ sys->dlsym(libHandle, "createHandler"));
- const char* error = dlerror();
+ const char* error = sys->dlerror();
if (error)
{
log<level::ERR>("ERROR loading symbol",
- entry("HANDLER=%s", ps.c_str()),
+ entry("HANDLER=%s", p.c_str()),
entry("ERROR=%s", error));
continue;
}
@@ -78,7 +73,7 @@
if (!result)
{
log<level::ERR>("Unable to create handler",
- entry("HANDLER=%s", ps.c_str()));
+ entry("HANDLER=%s", p.c_str()));
continue;
}
diff --git a/utils.hpp b/utils.hpp
index 9b75a40..50d320e 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -1,15 +1,34 @@
#pragma once
+#include "internal/sys.hpp"
+
+#include <blobs-ipmid/manager.hpp>
+#include <memory>
#include <string>
namespace blobs
{
+using HandlerFactory = std::unique_ptr<GenericBlobInterface> (*)();
+
+/**
+ * The bitbake recipe symlinks the library lib*.so.? into the folder
+ * only, and not the other names, .so, .so.?.?, .so.?.?.?
+ *
+ * Therefore only care if it's lib*.so.?
+ *
+ * @param[in] the path to check.
+ * @return true if matches, false otherwise
+ */
+bool matchBlobHandler(const std::string& filename);
/**
* @brief Given a path, find libraries (*.so only) and load them.
*
+ * @param[in] manager - pointer to a manager
* @param[in] paths - list of fully qualified paths to libraries to load.
+ * @param[in] sys - pointer to implementation of the dlsys interface.
*/
-void loadLibraries(const std::string& path);
+void loadLibraries(ManagerInterface* manager, const std::string& path,
+ const internal::DlSysInterface* sys = &internal::dlsys_impl);
} // namespace blobs