[cpp-sp] branch main updated: Semi-finalized port of XML config support with tests.

Scott Cantor cantor.2 at osu.edu
Thu Dec 5 19:11:45 UTC 2024


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository cpp-sp.

View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=8736c8a18a2726b131fcf89c055cac5355263483

The following commit(s) were added to refs/heads/main by this push:
     new 8736c8a1 Semi-finalized port of XML config support with tests.
8736c8a1 is described below

commit 8736c8a18a2726b131fcf89c055cac5355263483
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Dec 5 14:11:40 2024 -0500

    Semi-finalized port of XML config support with tests.
---
 shibsp/Makefile.am                                 |   4 +-
 .../{ReloadableFile.cpp => ReloadableXMLFile.cpp}  | 112 +++++-----
 .../util/{ReloadableFile.h => ReloadableXMLFile.h} |  62 +++---
 tests/Makefile.am                                  |   2 +-
 .../data/util/reloadablefile/external-invalid.xml  |   1 +
 tests/data/util/reloadablefile/external.xml        |   2 +-
 tests/data/util/reloadablefile/inline-invalid.xml  |   3 +
 tests/data/util/reloadablefile/inline.xml          |   7 +-
 tests/data/util/reloadablefile/invalid.xml         |   1 +
 tests/util/ReloadableFileTests.cpp                 | 123 -----------
 tests/util/ReloadableXMLFileTests.cpp              | 225 +++++++++++++++++++++
 11 files changed, 330 insertions(+), 212 deletions(-)

diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index a531ea03..df56c005 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -74,7 +74,7 @@ utilinclude_HEADERS = \
 	util/Lockable.h \
 	util/PathResolver.h \
 	util/PropertySet.h \
-	util/ReloadableFile.h \
+	util/ReloadableXMLFile.h \
 	util/SPConstants.h \
 	util/TemplateParameters.h \
 	util/URLEncoder.h
@@ -146,7 +146,7 @@ libshibsp_la_SOURCES = \
 	util/DOMPropertySet.cpp \
 	util/IPRange.cpp \
 	util/PathResolver.cpp \
-	util/ReloadableFile.cpp \
+	util/ReloadableXMLFile.cpp \
 	util/SPConstants.cpp \
 	util/TemplateParameters.cpp \
 	util/URLEncoder.cpp
diff --git a/shibsp/util/ReloadableFile.cpp b/shibsp/util/ReloadableXMLFile.cpp
similarity index 55%
rename from shibsp/util/ReloadableFile.cpp
rename to shibsp/util/ReloadableXMLFile.cpp
index 5e4f9a45..a9875b11 100644
--- a/shibsp/util/ReloadableFile.cpp
+++ b/shibsp/util/ReloadableXMLFile.cpp
@@ -1,27 +1,21 @@
 /**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
+ * 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
  *
- * UCAID licenses this file to you 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
  *
- * 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.
+ * 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.
  */
 
 /**
- * util/ReloadableFile.cpp
+ * util/ReloadableXMLFile.cpp
  *
- * Base class for file-based configuration.
+ * Base class for XML file-based configuration.
  */
 
 #include "internal.h"
@@ -29,7 +23,7 @@
 #include "AgentConfig.h"
 #include "logging/Category.h"
 #include "util/PathResolver.h"
-#include "util/ReloadableFile.h"
+#include "util/ReloadableXMLFile.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -40,7 +34,7 @@ using namespace shibsp;
 using namespace std;
 
 namespace {
-    // More an experiment than anything but it does encapsulate the conversion.
+    // More an experiment than anything but it does encapsulate the conversion...
     struct string_to_bool_translator {
         typedef std::string internal_type;
         typedef bool external_type;
@@ -57,10 +51,11 @@ namespace {
     };
 };
 
-const char ReloadableFile::PATH_PROP_NAME[] = "path";
-const char ReloadableFile::RELOAD_CHANGES_PROP_NAME[] = "reloadChanges";
+const char ReloadableXMLFile::PATH_PROP_NAME[] = "<xmlattr>.path";
+const char ReloadableXMLFile::RELOAD_CHANGES_PROP_NAME[] = "<xmlattr>.reloadChanges";
 
-ReloadableFile::ReloadableFile(const ptree& pt, Category& log) : m_root(pt), m_log(log), m_filestamp(0)
+ReloadableXMLFile::ReloadableXMLFile(const string& rootElementName, const ptree& pt, Category& log)
+    : m_root(pt), m_log(log), m_rootElementName(rootElementName), m_filestamp(0)
 #ifdef HAVE_CXX17
         , m_lock(nullptr)
 #elif HAVE_CXX14
@@ -74,6 +69,12 @@ ReloadableFile::ReloadableFile(const ptree& pt, Category& log) : m_root(pt), m_l
 
         string_to_bool_translator tr;
         bool reloadChanges = pt.get(RELOAD_CHANGES_PROP_NAME, false, tr);
+#ifndef HAVE_CXX14
+        if (reloadChanges) {
+            log.warn("C++ compiler level does not allow for reloadChanges, ignoring");
+            reloadChanges = false;
+        }
+#endif
         log.info("using path (%s), will %smonitor for changes", m_source.c_str(), reloadChanges ? "" : "not ");
         if (reloadChanges) {
 #ifdef HAVE_CXX17
@@ -85,16 +86,16 @@ ReloadableFile::ReloadableFile(const ptree& pt, Category& log) : m_root(pt), m_l
     }
 }
 
-ReloadableFile::~ReloadableFile()
+ReloadableXMLFile::~ReloadableXMLFile()
 {
 }
 
-time_t ReloadableFile::getLastModified() const
+time_t ReloadableXMLFile::getLastModified() const
 {
     return m_filestamp;
 }
 
-bool ReloadableFile::isUpdated() const
+bool ReloadableXMLFile::isUpdated() const
 {
     if (m_source.empty()) {
         return false;
@@ -114,7 +115,7 @@ bool ReloadableFile::isUpdated() const
     return stat_buf.st_mtime > m_filestamp;
 }
 
-void ReloadableFile::updateModificationTime()
+void ReloadableXMLFile::updateModificationTime()
 {
 #ifdef WIN32
     struct _stat stat_buf;
@@ -127,57 +128,68 @@ void ReloadableFile::updateModificationTime()
     }
 }
 
-void ReloadableFile::updateModificationTime(time_t t)
+void ReloadableXMLFile::updateModificationTime(time_t t)
 {
     m_filestamp = t;
 }
 
-pair<bool,ptree*> ReloadableFile::load()
+pair<bool,ptree*> ReloadableXMLFile::load() noexcept
 {
-    if (m_source.empty()) {
-        m_log.debug("loading inline configuration...");
-        // Data comes from the tree we were handed.
-        // Because property trees work differently from an XML DOM,
-        // we return the actual root, and not the first child as before
-        // so the caller can interrogate the name of the child tree to
-        // ensure it's as expected.
-        // The const_cast is safe because the flag is false,
-        // preventing the caller from retaining ownership.
-        return make_pair(false, const_cast<ptree*>(&m_root));
-    }
-
     try {
+        if (m_source.empty()) {
+            m_log.debug("loading inline configuration...");
+            // Data comes from the tree we were handed by locating a subtree of the expected name.
+            const boost::optional<const ptree&> child = m_root.get_child_optional(m_rootElementName);
+            if (!child) {
+                throw xml_parser_error("XML missing expected child element: " + m_rootElementName, "inline", 0);
+            }
+            // The const_cast is safe because the flag is false,
+            // preventing the caller from retaining ownership.
+            return make_pair(false, const_cast<ptree*>(&m_root));
+        }
+
         unique_ptr<ptree> newtree = unique_ptr<ptree>(new ptree());
         xml_parser::read_xml(m_source, *newtree, xml_parser::no_comments|xml_parser::trim_whitespace);
+
+        // Data comes from the tree we were handed by locating a subtree of the expected name.
+        const boost::optional<ptree&> child = newtree->get_child_optional(m_rootElementName);
+        if (!child) {
+            throw xml_parser_error("XML missing expected child element: " + m_rootElementName, m_source, 0);
+        }
+
         return make_pair(true, newtree.release());
     } catch (const bad_alloc& e) {
         m_log.crit("out of memory parsing XML configuration (%s)", m_source.c_str());
     } catch (const xml_parser_error& e) {
-        m_log.error("failed to process XML configuration (%s): %s", m_source.c_str(), e.what());
+        m_log.error("failed to process XML configuration: %s", e.what());
     }
+
+    return make_pair(false, nullptr);
 }
 
-void ReloadableFile::lock()
+void ReloadableXMLFile::lock()
 {
     if (m_lock) {
         m_lock->lock();
     }
 }
 
-bool ReloadableFile::try_lock()
+bool ReloadableXMLFile::try_lock()
 {
     if (m_lock) {
         return m_lock->try_lock();
+    } else {
+        return true;
     }
 }
 
-void ReloadableFile::unlock()
+void ReloadableXMLFile::unlock()
 {
     if (m_lock)
         m_lock->unlock();
 }
 
-void ReloadableFile::lock_shared()
+void ReloadableXMLFile::lock_shared()
 {
     if (!m_lock) {
         return;
@@ -198,16 +210,12 @@ void ReloadableFile::lock_shared()
     // The result is handled entirely by the subclass so is ignored here.
     // The original root tree is purely a means of communicating the object
     // from the c'tor over to the load method for the inline case, at first load.
-    try {
-        load();
-    } catch (...) {
-        // Shouldn't happen but ensures we generally will acquire the lock before returning.
-    }
+    load();
 
     m_lock->lock_shared();
 }
 
-bool ReloadableFile::try_lock_shared()
+bool ReloadableXMLFile::try_lock_shared()
 {
     if (m_lock)
         return m_lock->try_lock_shared();
@@ -215,7 +223,7 @@ bool ReloadableFile::try_lock_shared()
         return true;
 }
 
-void ReloadableFile::unlock_shared()
+void ReloadableXMLFile::unlock_shared()
 {
     if (m_lock)
         m_lock->unlock_shared();
diff --git a/shibsp/util/ReloadableFile.h b/shibsp/util/ReloadableXMLFile.h
similarity index 75%
rename from shibsp/util/ReloadableFile.h
rename to shibsp/util/ReloadableXMLFile.h
index 394bbc34..4ec4b96c 100644
--- a/shibsp/util/ReloadableFile.h
+++ b/shibsp/util/ReloadableXMLFile.h
@@ -13,13 +13,13 @@
  */
 
 /**
- * @file shibsp/util/ReloadableFile.h
+ * @file shibsp/util/ReloadableXMLFile.h
  * 
- * Base class for reloadable file-based configuration.
+ * Base class for reloadable XML file-based configuration.
  */
 
-#ifndef __shibsp_reloadablefile_h__
-#define __shibsp_reloadablefile_h__
+#ifndef __shibsp_ReloadableXMLFile_h__
+#define __shibsp_ReloadableXMLFile_h__
 
 #include <shibsp/util/Lockable.h>
 
@@ -38,20 +38,29 @@ namespace shibsp {
     class SHIBSP_API Category;
 
     /**
-     * Base class for file-based configuration, provides locking and reload semantics.
+     * Base class for file-based XML configuration, provides locking and reload semantics.
      * 
      * <p>Also supports "inliine" configuration that short-circuits most of this logic
      * allowing for unified handling of the two cases by implementing classes and the
      * consumers of a configuration interface.</p>
      */
-    class SHIBSP_API ReloadableFile : public virtual BasicLockable, public virtual SharedLockable
+    class SHIBSP_API ReloadableXMLFile : public virtual BasicLockable, public virtual SharedLockable
     {
-        MAKE_NONCOPYABLE(ReloadableFile);
+        MAKE_NONCOPYABLE(ReloadableXMLFile);
 
     public:
         static const char PATH_PROP_NAME[];
         static const char RELOAD_CHANGES_PROP_NAME[];
 
+        // BasicLockable
+        void lock();
+        bool try_lock();
+        void unlock();
+        // SharedLockable
+        void lock_shared();
+        bool try_lock_shared();
+        void unlock_shared();
+
     protected:
         /**
          * Base class constructor.
@@ -64,12 +73,18 @@ namespace shibsp {
          * inline as the content of the supplied tree and the base class essentially
          * performs no activity, stubs out locking, etc.</p>
          * 
+         * <p>Note that the root element name specified applies to the content of the
+         * configuration itself and not the element that may be carrying the "path" key
+         * specifying an external file (i.e., in that case it's the external content whose
+         * root element would be expected to match).</p>
+         * 
          * @param pt                    root of property tree defining resource
+         * @param rootElementName       name of expexcted root element of XML configuration
          * @param log                   logging object to use
          */
-        ReloadableFile(const boost::property_tree::ptree& pt, Category& log);
+        ReloadableXMLFile(const std::string& rootElementName, const boost::property_tree::ptree& pt, Category& log);
     
-        virtual ~ReloadableFile();
+        virtual ~ReloadableXMLFile();
 
         /**
          * Loads (or reloads) configuration material.
@@ -78,17 +93,17 @@ namespace shibsp {
          * initially and any time a change is detected but is not called
          * initially unless by a subclass.</p>
          *
-         * <p>This method is not called with the object locked, so actual
-         * modification of configuration state requires explicit locking
-         * within the method.</p>
+         * <p>This method is not intended to throw.</p>
          * 
-         * <p>This method should NOT throw exceptions.</p>
+         * <p>This method is not called with the object locked, and it does
+         * not modify implementation state. Subclasses are expected to override
+         * this method to lock and modify state as required.</p>
          * 
-         * @return a pair containing a pointer to the property tree loaded
-         *  and a flag indicating whether the subclass should retain ownership
+         * @return a pair containing a (possibly null) pointer to the property tree
+         *  loaded and a flag indicating whether the subclass should retain ownership
          *  of the tree and free it when done with it
          */
-        virtual std::pair<bool,boost::property_tree::ptree*> load();
+        virtual std::pair<bool,boost::property_tree::ptree*> load() noexcept;
 
         /**
          * Gets the last time the configuration was updated.
@@ -143,6 +158,9 @@ namespace shibsp {
         /** Resource path. */
         std::string m_source;
 
+        /** Expected name of root element, i.e. subtree, after parsing. */
+        std::string m_rootElementName;
+
         /** Last modification of local resource. */
         time_t m_filestamp;
 
@@ -152,18 +170,8 @@ namespace shibsp {
 #elif HAVE_CXX14
         std::unique_ptr<std::shared_timed_mutex> m_lock;
 #endif
-
-    public:
-        // BasicLockable
-        void lock();
-        bool try_lock();
-        void unlock();
-        // SharedLockable
-        void lock_shared();
-        bool try_lock_shared();
-        void unlock_shared();
     };
 
 };
 
-#endif /* __shibsp_reloadablefile_h__ */
+#endif /* __shibsp_ReloadableXMLFile_h__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5db9ce9b..d66b43f2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -14,7 +14,7 @@ shibsptest_SOURCES = \
 	AgentConfigTests.cpp \
 	util/PropertyTreeTests.cpp \
 	util/BoostPropertySetTests.cpp \
-	util/ReloadableFileTests.cpp
+	util/ReloadableXMLFileTests.cpp
 
 shibsptest_LDADD = \
     $(top_builddir)/shibsp/libshibsp.la
diff --git a/tests/data/util/reloadablefile/external-invalid.xml b/tests/data/util/reloadablefile/external-invalid.xml
new file mode 100644
index 00000000..4e621a2f
--- /dev/null
+++ b/tests/data/util/reloadablefile/external-invalid.xml
@@ -0,0 +1 @@
+<RequestMapper type="XML" path="./data/util/reloadablefile/invalid.xml" reloadChanges="true" />
diff --git a/tests/data/util/reloadablefile/external.xml b/tests/data/util/reloadablefile/external.xml
index c2e362a9..23b9d0b4 100644
--- a/tests/data/util/reloadablefile/external.xml
+++ b/tests/data/util/reloadablefile/external.xml
@@ -1 +1 @@
-<RequestMapper type="XML" path="./data/util/reloadablefile/external.xml" reloadChanges="true" />
+<RequestMapper type="XML" path="./data/util/reloadablefile/requestmap.xml" reloadChanges="true" />
diff --git a/tests/data/util/reloadablefile/inline-invalid.xml b/tests/data/util/reloadablefile/inline-invalid.xml
new file mode 100644
index 00000000..3bb75e72
--- /dev/null
+++ b/tests/data/util/reloadablefile/inline-invalid.xml
@@ -0,0 +1,3 @@
+<RequestMapper type="XML">
+    <Foo/>
+</RequestMapper>
diff --git a/tests/data/util/reloadablefile/inline.xml b/tests/data/util/reloadablefile/inline.xml
index 977fc86f..8e8aca50 100644
--- a/tests/data/util/reloadablefile/inline.xml
+++ b/tests/data/util/reloadablefile/inline.xml
@@ -1,8 +1,3 @@
 <RequestMapper type="XML">
-    <RequestMap>
-        <Host name="sp.example.org">
-            <Path name="secure" requireSession="true" />
-        </Host>
-        <Host name="admin.example.org" applicationId="admin" requireSession="true" />
-    </RequestMap>
+    <RequestMap/>
 </RequestMapper>
diff --git a/tests/data/util/reloadablefile/invalid.xml b/tests/data/util/reloadablefile/invalid.xml
new file mode 100644
index 00000000..496e8bb8
--- /dev/null
+++ b/tests/data/util/reloadablefile/invalid.xml
@@ -0,0 +1 @@
+<Foo/>
diff --git a/tests/util/ReloadableFileTests.cpp b/tests/util/ReloadableFileTests.cpp
deleted file mode 100644
index e4660964..00000000
--- a/tests/util/ReloadableFileTests.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * ReloadableFileTests.cpp
- *
- * Unit tests for reloadable file usage.
- */
-
-#include "AgentConfig.h"
-#include "logging/Category.h"
-#include "util/ReloadableFile.h"
-
-#include <boost/test/unit_test.hpp>
-#include <boost/property_tree/xml_parser.hpp>
-
-using namespace shibsp;
-using namespace boost::property_tree;
-using namespace std;
-
-#define DATA_PATH "./data/util/reloadablefile/"
-
-struct Inline_Fixture {
-    Inline_Fixture() : data_path(DATA_PATH) {
-        AgentConfig::getConfig().init(nullptr, (data_path + "console-shibboleth.ini").c_str(), true);
-        xml_parser::read_xml(data_path + "inline.xml", tree, xml_parser::no_comments|xml_parser::trim_whitespace);
-    }
-    ~Inline_Fixture() {
-        AgentConfig::getConfig().term();
-    }
-
-    string data_path;
-    ptree tree;
-};
-
-class DummyXMLFile : virtual public ReloadableFile
-{
-public:
-    DummyXMLFile(const ptree& pt)
-        : ReloadableFile(pt, Category::getInstance("DummyXMLFile")),
-            m_log(Category::getInstance("DummyXMLFile")), m_tree(nullptr), m_forceReload(false) {
-
-        load();
-    }
-    ~DummyXMLFile() {}
-
-    bool isUpdated() const {
-        return m_forceReload;
-    }
-
-    void forceReload() {
-        m_forceReload = true;
-    }
-
-    time_t getLastModified() const {
-        return ReloadableFile::getLastModified();
-    }
-
-protected:
-    pair<bool,ptree*> load();
-
-private:
-    Category& m_log;
-    unique_ptr<ptree> m_tree;
-    bool m_forceReload;
-};
-
-pair<bool,ptree*> DummyXMLFile::load()
-{
-    pair<bool,ptree*> ret = ReloadableFile::load();
-    if (ret.second) {
-        if (ret.first) {
-            m_log.debug("external config is valid");
-        } else {
-            m_log.debug("inline config is valid");
-            return ret;
-        }
-    } else {
-        m_log.error("initial configuration was invalid");
-        return ret;
-    }
-
-    // Swap in external config and update timestamp.
-
-#ifdef HAVE_CXX14
-    unique_lock<ReloadableFile> locker(*this);
-#endif
-    unique_ptr<ptree> newtree(ret.second);
-    m_tree.swap(newtree);
-    updateModificationTime(time(nullptr));
-
-    return ret;
-}
-
-BOOST_FIXTURE_TEST_CASE(ReloadableFileTest_no_reload, Inline_Fixture)
-{
-    DummyXMLFile dummy(tree);
-
-    dummy.lock_shared();
-    time_t ts1 = dummy.getLastModified();
-    BOOST_CHECK_EQUAL(ts1, 0);
-    dummy.unlock();
-
-    // No-op since there's no locking internally.
-    dummy.forceReload();
-    sleep(2);
-
-    dummy.lock_shared();
-    time_t ts2 = dummy.getLastModified();
-    BOOST_CHECK_EQUAL(ts2, 0);
-    dummy.unlock();
-}
diff --git a/tests/util/ReloadableXMLFileTests.cpp b/tests/util/ReloadableXMLFileTests.cpp
new file mode 100644
index 00000000..62e85f10
--- /dev/null
+++ b/tests/util/ReloadableXMLFileTests.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+/**
+ * ReloadableFileTests.cpp
+ *
+ * Unit tests for reloadable file usage.
+ */
+
+#include "AgentConfig.h"
+#include "logging/Category.h"
+#include "util/ReloadableXMLFile.h"
+
+#include <boost/test/unit_test.hpp>
+#include <boost/property_tree/xml_parser.hpp>
+
+using namespace shibsp;
+using namespace boost::property_tree;
+using namespace std;
+
+#define DATA_PATH "./data/util/reloadablefile/"
+
+class DummyXMLFile : virtual public ReloadableXMLFile
+{
+public:
+    DummyXMLFile(const ptree& pt)
+        : ReloadableXMLFile("RequestMap", pt, Category::getInstance("DummyXMLFile")),
+            m_log(Category::getInstance("DummyXMLFile")), m_tree(nullptr), m_forceReload(false) {
+        if (!load().second) {
+            throw domain_error("Invalid configuration.");
+        }
+    }
+    ~DummyXMLFile() {}
+
+    bool isUpdated() const {
+        return m_forceReload;
+    }
+
+    void forceReload() {
+        m_forceReload = true;
+    }
+
+    time_t getLastModified() const {
+        return ReloadableXMLFile::getLastModified();
+    }
+
+protected:
+    pair<bool,ptree*> load() noexcept;
+
+private:
+    Category& m_log;
+    unique_ptr<ptree> m_tree;
+    bool m_forceReload;
+};
+
+pair<bool,ptree*> DummyXMLFile::load() noexcept
+{
+    pair<bool,ptree*> ret = ReloadableXMLFile::load();
+    if (ret.second) {
+
+        // For test-sake, re-verify the child element.
+        const boost::optional<ptree&> child = ret.second->get_child_optional("RequestMap");
+        if (!child) {
+            return make_pair(false, nullptr);
+        }
+
+        if (ret.first) {
+            m_log.debug("external config is valid");
+        } else {
+            m_log.debug("inline config is valid");
+            return ret;
+        }
+    } else {
+        return make_pair(false, nullptr);
+    }
+
+    // Swap in external config and update timestamp.
+
+#ifdef HAVE_CXX14
+    unique_lock<ReloadableXMLFile> locker(*this);
+#endif
+    unique_ptr<ptree> newtree(ret.second);
+    m_tree.swap(newtree);
+    updateModificationTime(time(nullptr));
+
+    return ret;
+}
+
+class exceptionCheck {
+public:
+    exceptionCheck(const string& msg) : m_msg(msg) {}
+    bool check_message(const exception& e) {
+        cout << e.what() << endl;
+        return m_msg.compare(e.what()) == 0;
+    }
+private:
+    string m_msg;
+};
+
+struct BaseFixture
+{
+    BaseFixture() : data_path(DATA_PATH) {
+        AgentConfig::getConfig().init(nullptr, (data_path + "console-shibboleth.ini").c_str(), true);
+    }
+    ~BaseFixture() {
+        AgentConfig::getConfig().term();
+    }
+    string data_path;
+};
+
+/////////////
+
+struct External_Invalid_Fixture : public BaseFixture
+{
+    External_Invalid_Fixture() {
+        xml_parser::read_xml(data_path + "external-invalid.xml", tree, xml_parser::no_comments|xml_parser::trim_whitespace);
+    }
+    ~External_Invalid_Fixture() {
+    }
+
+    ptree tree;
+};
+
+BOOST_FIXTURE_TEST_CASE(ReloadableFileTest_external_invalid, External_Invalid_Fixture)
+{
+    BOOST_CHECK_EQUAL(tree.size(), 1);
+
+    exceptionCheck checker("Invalid configuration.");
+    BOOST_CHECK_EXCEPTION(DummyXMLFile dummy(tree.front().second), domain_error, checker.check_message);
+}
+
+/////////////
+
+struct Inline_Invalid_Fixture : public BaseFixture
+{
+    Inline_Invalid_Fixture() {
+        xml_parser::read_xml(data_path + "inline-invalid.xml", tree, xml_parser::no_comments|xml_parser::trim_whitespace);
+    }
+    ~Inline_Invalid_Fixture() {
+    }
+
+    ptree tree;
+};
+
+BOOST_FIXTURE_TEST_CASE(ReloadableFileTest_inline_invalid, Inline_Invalid_Fixture)
+{
+    BOOST_CHECK_EQUAL(tree.size(), 1);
+
+    exceptionCheck checker("Invalid configuration.");
+    BOOST_CHECK_EXCEPTION(DummyXMLFile dummy(tree.front().second), domain_error, checker.check_message);
+}
+
+/////////////
+
+struct Inline_Valid_Fixture : public BaseFixture
+{
+    Inline_Valid_Fixture() {
+        xml_parser::read_xml(data_path + "inline.xml", tree, xml_parser::no_comments|xml_parser::trim_whitespace);
+    }
+    ~Inline_Valid_Fixture() {
+    }
+
+    ptree tree;
+};
+
+BOOST_FIXTURE_TEST_CASE(ReloadableFileTest_inline_valid, Inline_Valid_Fixture)
+{
+    BOOST_CHECK_EQUAL(tree.size(), 1);
+    DummyXMLFile dummy(tree.front().second);
+
+    dummy.lock_shared();
+    time_t ts1 = dummy.getLastModified();
+    BOOST_CHECK_EQUAL(ts1, 0);
+    dummy.unlock();
+
+    // No-op since there's no locking internally.
+    dummy.forceReload();
+    sleep(2);
+
+    dummy.lock_shared();
+    time_t ts2 = dummy.getLastModified();
+    BOOST_CHECK_EQUAL(ts2, 0);
+    dummy.unlock();
+}
+
+struct External_Valid_Fixture : public BaseFixture
+{
+    External_Valid_Fixture() {
+        xml_parser::read_xml(data_path + "external.xml", tree, xml_parser::no_comments|xml_parser::trim_whitespace);
+    }
+    ~External_Valid_Fixture() {
+    }
+
+    ptree tree;
+};
+
+BOOST_FIXTURE_TEST_CASE(ReloadableFileTest_external_valid, External_Valid_Fixture)
+{
+    BOOST_CHECK_EQUAL(tree.size(), 1);
+    DummyXMLFile dummy(tree.front().second);
+
+    dummy.lock_shared();
+    time_t ts1 = dummy.getLastModified();
+    BOOST_CHECK_GT(ts1, 0);
+    dummy.unlock();
+
+    dummy.forceReload();
+    sleep(2);
+
+    dummy.lock_shared();
+    time_t ts2 = dummy.getLastModified();
+    BOOST_CHECK_GT(ts2, ts1);
+    dummy.unlock();
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list