diff --git a/Doxyfile b/Doxyfile index 38bd60e9f..e73312580 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ PROJECT_NAME = Autowiring # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = "0.5.0" +PROJECT_NUMBER = "0.5.1" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer diff --git a/autowiring/AutoConfig.h b/autowiring/AutoConfig.h index f470ea943..cc7b5c405 100644 --- a/autowiring/AutoConfig.h +++ b/autowiring/AutoConfig.h @@ -40,9 +40,21 @@ class AutoConfig: public AutoConfigBase { public: - static_assert(sizeof...(TKey)==1 || sizeof...(TKey)==2, "Must provide a key and optional namespace"); + static_assert(sizeof...(TKey) >= 1, "Must provide a key and optionally at least one namespace"); - AutoConfig(void) : + template + AutoConfig(t_Arg &&arg, t_Args&&... args) : + AutoConfigBase(typeid(ConfigTypeExtractor)) + { + // Register with config registry + (void)RegConfig::r; + + if (!IsConfigured()){ + m_manager->Set(m_key, T(std::forward(arg), std::forward(args)...)); + } + } + + AutoConfig() : AutoConfigBase(typeid(ConfigTypeExtractor)) { // Register with config registry @@ -61,6 +73,10 @@ class AutoConfig: return m_manager->Get(m_key)->template as().get(); } + void operator=(const T& newValue) { + return m_manager->Set(m_key, newValue); + } + /// /// True if this configurable field has been satisfied with a value /// diff --git a/autowiring/AutoConfigManager.h b/autowiring/AutoConfigManager.h index cd62c9090..03a34a5ac 100644 --- a/autowiring/AutoConfigManager.h +++ b/autowiring/AutoConfigManager.h @@ -21,6 +21,8 @@ class AutoConfigManager: // Callback function type typedef std::function t_callback; + + typedef std::function t_add_callback; // Validator function type typedef std::function t_validator; @@ -41,9 +43,15 @@ class AutoConfigManager: // Set of keys for values set from this context std::unordered_set m_setHere; + // list of keys for values set from this context in order of creation. + std::vector m_orderedKeys; + // map of callbacks registered for a key std::unordered_map> m_callbacks; + // list of callbacks registered for keys which exist in this context. + std::list m_addCallbacks; + // Exception throwers: void ThrowKeyNotFoundException(const std::string& key) const; void ThrowTypeMismatchException(const std::string& key, const std::type_info& ti) const; @@ -67,7 +75,7 @@ class AutoConfigManager: /// in the application, or if the specified value type does not match the type expected by this field /// AnySharedPointer& Get(const std::string& key); - + /// /// Assigns the specified value to an AnySharedPointer slot /// @@ -106,7 +114,14 @@ class AutoConfigManager: // Add a callback for when key is changed in this context void AddCallback(const std::string& key, t_callback&& fx); - + + // Add a callback for when a key is set in this context. Is immediately called on all + // currently existing values in the order they were created + void AddCallback(t_add_callback&& fx); + + // Returns a list of all keys which have been set from this context + const std::vector& GetLocalKeys() const { return m_orderedKeys; } + private: // Handles setting a value recursivly to all child contexts void SetRecursive(const std::string& key, AnySharedPointer value); diff --git a/autowiring/AutowiringEnclosure.h b/autowiring/AutowiringEnclosure.h index df069e0ff..16053a723 100644 --- a/autowiring/AutowiringEnclosure.h +++ b/autowiring/AutowiringEnclosure.h @@ -102,11 +102,10 @@ class AutowiringEnclosure: auto ctxt = ecef ? ecef->GetContext() : nullptr; ctxt->SignalShutdown(); - // Do not allow teardown to take more than 250 milliseconds or so - if(!ctxt->Wait(std::chrono::milliseconds(250))) { - // Critical error--took too long to tear down - assert(false); - } + // Do not allow teardown to take more than 5 seconds. This is considered a "timely teardown" limit. + // If it takes more than this amount of time to tear down, the test case itself should invoke SignalShutdown + // and Wait itself with the extended teardown period specified. + ASSERT_TRUE(ctxt->Wait(std::chrono::seconds(5))) << "Test case took too long to tear down, unit tests running after this point are untrustworthy"; static const char sc_autothrow [] = "AUTOTHROW_"; if(!strncmp(sc_autothrow, info.name(), sizeof(sc_autothrow) - 1)) diff --git a/autowiring/ContextEnumerator.h b/autowiring/ContextEnumerator.h index 32ca272ee..f4fd9c484 100644 --- a/autowiring/ContextEnumerator.h +++ b/autowiring/ContextEnumerator.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "autowiring_error.h" #include MEMORY_HEADER class CoreContext; @@ -26,6 +27,11 @@ class CoreContextT; class ContextEnumerator { public: + /// + /// Constructs a context enumerator for the current context + /// + ContextEnumerator(void); + /// /// Constructs an enumerator which may enumerate all of the contexts rooted at the specified root /// @@ -41,7 +47,7 @@ class ContextEnumerator /// The iterator class which is actually used in enumerating contexts class iterator { public: - typedef std::input_iterator_tag iterator_category; + typedef std::forward_iterator_tag iterator_category; typedef const std::shared_ptr& value_type; typedef size_t difference_type; typedef const std::shared_ptr* pointer; @@ -67,8 +73,10 @@ class ContextEnumerator // Operator overloads: const iterator& operator++(void); + iterator operator++(int); + const std::shared_ptr& operator*(void) const { return m_cur; } - const CoreContext& operator->(void) const { return ***this; } + const std::shared_ptr& operator->(void) const { return m_cur; } bool operator==(const iterator& rhs) const { return m_root == rhs.m_root && m_cur == rhs.m_cur; } bool operator!=(const iterator& rhs) const { return !(*this == rhs); } explicit operator bool(void) const { return !!m_cur.get(); } @@ -96,7 +104,9 @@ class ContextEnumeratorT: public ContextEnumerator { public: - ContextEnumeratorT(const std::shared_ptr& ctxt): + ContextEnumeratorT() + {} + ContextEnumeratorT(const std::shared_ptr& ctxt) : ContextEnumerator(ctxt) {} @@ -126,10 +136,29 @@ class ContextEnumeratorT: return *this; } + iterator operator++(int) { + auto retVal = *this; + ContextEnumerator::iterator::operator++(0); + _advance(); + return retVal; + } + std::shared_ptr> operator*(void) const { return std::static_pointer_cast>(m_cur); } const CoreContext& operator->(void) const { return ***this; } }; + // Convenience routine for returning a single unique element + std::shared_ptr unique(void) { + iterator q = begin(); + iterator r = q; + + // If advancing q gets us to the end, then we only have one element and we can return success + if (++q == end()) + return *r; + + throw autowiring_error("Attempted to get a unique context on a context enumerator that enumerates more than one child"); + } + // Standard STL duck interface methods: iterator begin(void) { return iterator(m_root, m_root); }; iterator end(void) { return iterator(m_root); } diff --git a/autowiring/ContextMap.h b/autowiring/ContextMap.h index b987b1500..89ea3bc2c 100644 --- a/autowiring/ContextMap.h +++ b/autowiring/ContextMap.h @@ -2,7 +2,7 @@ #pragma once #include "autowiring_error.h" #include "CoreContext.h" -#include STL_UNORDERED_MAP +#include extern std::shared_ptr NewContextThunk(void); @@ -30,40 +30,92 @@ class ContextMap tracker(void): destroyed(false) {} + + // True when the enclosing context map has been destroyed bool destroyed; }; ContextMap(void): - m_tracker(new tracker), - m_lk(*m_tracker) + m_tracker(std::make_shared()) { } ~ContextMap(void) { // Teardown pathway assurance: - (std::lock_guard)m_lk, - (m_tracker->destroyed = true); - - // Done, we can release our copy of the shared pointer and end here - m_tracker.reset(); + std::lock_guard lk(*m_tracker); + m_tracker->destroyed = true; } private: // Tracker lock, used to protect against accidental destructor-contending access while still allowing // the parent ContextMap structure to be stack-allocated - std::shared_ptr m_tracker; - - typedef std::unordered_map> t_mpType; - std::mutex& m_lk; + const std::shared_ptr m_tracker; + typedef std::map> t_mpType; t_mpType m_contexts; public: // Accessor methods: size_t size(void) const {return m_contexts.size();} + class iterator { + public: + iterator(ContextMap& parent) : + parent(parent) + { + std::lock_guard lk(*parent.m_tracker); + + // Advance to the first iterator we can actually lock down: + iter = parent.m_contexts.begin(); + while ( + iter != parent.m_contexts.end() && + !(ctxt = iter->second.lock()) + ) + // Failure, next entry + iter++; + } + + iterator(ContextMap& parent, typename t_mpType::iterator iter, std::shared_ptr ctxt) : + parent(parent), + iter(iter), + ctxt(ctxt) + {} + + private: + ContextMap& parent; + typename t_mpType::iterator iter; + std::shared_ptr ctxt; + + public: + const iterator& operator++(void) { + // We need to ensure our local shared pointer doesn't go away until after we have + // advanced to the next entry + std::shared_ptr deferred = std::move(ctxt); + + // Loop until we get another stable entry: + std::lock_guard lk(*parent.m_tracker); + do iter++; + while (iter != parent.m_contexts.end() && !(ctxt = iter->second.lock())); + + // We can safely unlock because the current entry won't be evicted automatically as long as + // the shared pointer reference is held down. + return *this; + } + + const std::shared_ptr& operator*(void) const { return ctxt; } + const CoreContext& operator->(void) const { return *ctxt; } + bool operator==(const iterator& rhs) const { return &parent == &rhs.parent && iter == rhs.iter; } + bool operator!=(const iterator& rhs) const { return !(*this == rhs); } + explicit operator bool(void) const { + return ctxt; + } + }; + + iterator begin(void) { return iterator(*this); } + iterator end(void) { return iterator(*this, m_contexts.end(), nullptr); } + template void Enumerate(Fn&& fn) { - std::lock_guard lk(m_lk); + std::lock_guard lk(*m_tracker); for(const auto& entry : m_contexts) { auto ctxt = entry.second.lock(); if(ctxt && !fn(entry.first, ctxt)) @@ -81,7 +133,7 @@ class ContextMap /// An exception will be thrown if the passed key is already associated with a context /// void Add(const Key& key, std::shared_ptr& context) { - std::lock_guard lk(m_lk); + std::lock_guard lk(*m_tracker); auto& rhs = m_contexts[key]; if(!rhs.expired()) throw autowiring_error("Specified key is already associated with another context"); @@ -126,8 +178,8 @@ class ContextMap /// /// Attempts to find a context by the specified key /// - std::shared_ptr Find(const Key& key) { - std::lock_guard lk(m_lk); + std::shared_ptr Find(const Key& key) const { + std::lock_guard lk(*m_tracker); auto q = m_contexts.find(key); return @@ -135,4 +187,11 @@ class ContextMap std::shared_ptr() : q->second.lock(); } + + /// + /// Identical to Find + /// + std::shared_ptr operator[](const Key& key) const { + return Find(key); + } }; diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index c1cf9c06d..a828b81f1 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -161,7 +161,8 @@ class CoreContext: /// struct MemoEntry { MemoEntry(void) : - pFirst(nullptr) + pFirst(nullptr), + m_local(true) {} // The first deferrable autowiring which requires this type, if one exists: @@ -174,6 +175,9 @@ class CoreContext: // Once this memo entry is satisfied, this will contain the AnySharedPointer instance that performs // the satisfaction AnySharedPointer m_value; + + // A flag to determine if this memo entry was set from the current context. + bool m_local; }; protected: @@ -345,7 +349,7 @@ class CoreContext: /// /// Updates all deferred autowiring fields, generally called after a new member has been added /// - void UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry); + void UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry, bool local); /// \internal /// @@ -432,13 +436,13 @@ class CoreContext: /// The memo entry where this type was found /// /// An initialized shared pointer slot which may be used in type detection - void FindByType(AnySharedPointer& reference) const; + void FindByType(AnySharedPointer& reference, bool localOnly = false) const; /// \internal /// /// Unsynchronized version of FindByType /// - MemoEntry& FindByTypeUnsafe(AnySharedPointer& reference) const; + MemoEntry& FindByTypeUnsafe(AnySharedPointer& reference, bool localOnly = false) const; /// \internal /// @@ -695,7 +699,7 @@ class CoreContext: // member does not inherit CoreObject and this member is eventually satisfied by one that does. { std::shared_ptr pure; - FindByType(pure); + FindByType(pure, true); //skip non-local slots. if (pure) return pure; } @@ -1047,9 +1051,9 @@ class CoreContext: /// Locates an available context member in this context /// template - void FindByType(std::shared_ptr& slot) const { + void FindByType(std::shared_ptr& slot, bool localOnly = false) const { AnySharedPointerT reference; - FindByType(reference); + FindByType(reference, localOnly); slot = reference.slot()->template as(); } diff --git a/autowiring/CoreObjectDescriptor.h b/autowiring/CoreObjectDescriptor.h index 95fa92242..532d14160 100644 --- a/autowiring/CoreObjectDescriptor.h +++ b/autowiring/CoreObjectDescriptor.h @@ -26,7 +26,7 @@ struct CoreObjectDescriptor { template CoreObjectDescriptor(const std::shared_ptr& value, T*) : type(&typeid(T)), - actual_type(&typeid(*value)), + actual_type(&typeid(auto_id)), stump(&SlotInformationStump::s_stump), value(value), subscriber(MakeAutoFilterDescriptor(value)), diff --git a/contrib/autoboost/autoboost/thread/future.hpp b/contrib/autoboost/autoboost/thread/future.hpp index b8d00f501..f7564d784 100644 --- a/contrib/autoboost/autoboost/thread/future.hpp +++ b/contrib/autoboost/autoboost/thread/future.hpp @@ -57,6 +57,7 @@ #include #ifdef AUTOBOOST_THREAD_USES_CHRONO #include +#include #endif #if defined AUTOBOOST_THREAD_PROVIDES_FUTURE_CTOR_ALLOCATORS @@ -1391,8 +1392,14 @@ namespace autoboost wait_for(const chrono::duration& rel_time) const { return wait_until(chrono::steady_clock::now() + rel_time); + } + template + future_status wait_for(const std::chrono::duration& rel_time) const + { + return wait_for(chrono::duration>(rel_time.count())); } + template future_status wait_until(const chrono::time_point& abs_time) const diff --git a/publicDoxyfile.conf b/publicDoxyfile.conf index c56e74b41..1388a2fd6 100644 --- a/publicDoxyfile.conf +++ b/publicDoxyfile.conf @@ -38,7 +38,7 @@ PROJECT_NAME = Autowiring # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.5.0 +PROJECT_NUMBER = 0.5.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/src/autonet/AutoNetServerImpl.cpp b/src/autonet/AutoNetServerImpl.cpp index 4d697cd8d..ae96960aa 100644 --- a/src/autonet/AutoNetServerImpl.cpp +++ b/src/autonet/AutoNetServerImpl.cpp @@ -83,7 +83,14 @@ void AutoNetServerImpl::Run(void){ }); PollThreadUtilization(std::chrono::milliseconds(1000)); - CoreThread::Run(); + + try { + CoreThread::Run(); + } + catch (...) { + Stop(); + throw; + } } void AutoNetServerImpl::OnStop(void) { diff --git a/src/autowiring/AutoConfigManager.cpp b/src/autowiring/AutoConfigManager.cpp index 7b6a333d3..f7c4b3948 100644 --- a/src/autowiring/AutoConfigManager.cpp +++ b/src/autowiring/AutoConfigManager.cpp @@ -105,11 +105,21 @@ void AutoConfigManager::AddCallback(const std::string& key, t_callback&& fx) { m_callbacks[key].push_back(fx); } +void AutoConfigManager::AddCallback(t_add_callback&& fx) { + // Grab lock until done setting value + std::lock_guard lk(m_lock); + + for (auto& key : m_orderedKeys) + fx(key, m_values[key]); + + m_addCallbacks.emplace_back(std::move(fx)); +} + void AutoConfigManager::SetRecursive(const std::string& key, AnySharedPointer value) { // Call all validators for this key if (s_validators.count(key)) { for (auto const& fx : s_validators.find(key)->second) { - if (!fx(value)){ + if (!fx(value)) { std::stringstream ss; ss << "Attempted to set key '" << key << "'which didin't pass validator"; throw autowiring_error(ss.str()); @@ -118,13 +128,21 @@ void AutoConfigManager::SetRecursive(const std::string& key, AnySharedPointer va } // Grab lock until done setting value - std::lock_guard lk(m_lock); - + std::unique_lock lk(m_lock); + // Actually set the value in this manager SetInternal(key, value); // Mark key set from this manager - m_setHere.insert(key); + auto inserted = m_setHere.insert(key); + if (inserted.second) { + m_orderedKeys.push_back(key); + for (auto& callback : m_addCallbacks) { + lk.unlock(); + callback(key, value); + lk.lock(); + } + } // Enumerate descendant contexts auto enumerator = ContextEnumerator(GetContext()); @@ -151,10 +169,9 @@ void AutoConfigManager::SetRecursive(const std::string& key, AnySharedPointer va //Actaully set the value mgmt->SetInternal(key, value); - - // Continue to next context - ++ctxt; } + // Continue to next context + ++ctxt; } } @@ -162,7 +179,6 @@ void AutoConfigManager::SetInternal(const std::string& key, const AnySharedPoint m_values[key] = value; // Call callbacks for this key - for (const auto& cb : m_callbacks[key]) { + for (const auto& cb : m_callbacks[key]) cb(value); - } } diff --git a/src/autowiring/AutoConfigParser.cpp b/src/autowiring/AutoConfigParser.cpp index 2ba92b5e0..5f1179859 100644 --- a/src/autowiring/AutoConfigParser.cpp +++ b/src/autowiring/AutoConfigParser.cpp @@ -2,79 +2,47 @@ #include "stdafx.h" #include "AutoConfigParser.hpp" #include "demangle.h" -#include "expect.hpp" +#include - -std::string autowiring::ExtractKeyUnix(std::stringstream& ss) { +std::string autowiring::ExtractKey(const std::string& demangled) { //Extract Namespace and value from typename - //ConfigTypeExtractor - - std::string arg1; - std::string arg2; - ss >> expect("ConfigTypeExtractor<"); - ss >> arg1; - - // If arg1 contains a comma, there are 2 arguments - auto found = arg1.find(","); - if (found != std::string::npos) { - ss >> arg2; - - // Remove trailing "," - arg1.resize(arg1.size()-1); - - // Remove trailing '>' - arg2.resize(arg2.size()-1); - - std::stringstream key; - key << arg1 << "." << arg2; - return key.str(); - - } else { - // Remove trailing '>' - arg1.resize(arg1.size()-1); - return arg1; - } -} + //struct ConfigTypeExtractor + //Or on unix: ConfigTypeExtractor -std::string autowiring::ExtractKeyWin(std::stringstream& ss) { - //Extract Namespace and value from typename - //struct ConfigTypeExtractor - - std::string arg1; - std::string arg2; - ss >> expect("struct") >> expect("ConfigTypeExtractor> arg1; + const auto identifiersStart = demangled.find('<'); + const auto identifiersEnd = demangled.rfind('>'); + if (identifiersStart == (std::string::npos) || identifiersEnd == std::string::npos) + return std::string(); + + std::string key; - // If arg1 contains a comma, there are 2 arguments - auto found = arg1.find(",struct"); - if (found != std::string::npos) { - ss >> arg2; - - // Remove trailing ",struct" - arg1 = arg1.substr(0, found); - - // Remove trailing '>' - arg2.resize(arg2.size()-1); - - std::stringstream key; - key << arg1 << "." << arg2; - return key.str(); + auto subKeyStart = identifiersStart; + while (subKeyStart < identifiersEnd) { + //Find the > or , which denotes the start of the next identifier (or the end of the identifiers) + auto subKeyEnd = demangled.find_first_of(",>", subKeyStart); + + //Find the end of :: or the space between the struct declaration and our name backwards from the end of the identifer - } else { - // Remove trailing '>' - arg1.resize(arg1.size()-1); - return arg1; + auto subKeyStartTrimmed = demangled.find_last_of(" <,", subKeyEnd-1); + if(subKeyStartTrimmed != std::string::npos && subKeyStartTrimmed > subKeyStart) + subKeyStart = subKeyStartTrimmed; + + ++subKeyStart; //skip the space, :, or < that comes at the start + key += demangled.substr(subKeyStart, (subKeyEnd-subKeyStart)); + key += "."; + + subKeyStart = subKeyEnd + 1; } + + key.resize(key.size() - 1); //trim the trailing . + return key; } std::string autowiring::ExtractKey(const std::type_info& ti) { std::string demangled = demangle(ti); - std::stringstream ss(demangled); - - return -#if __GNUG__// Mac and linux - ExtractKeyUnix(ss); -#else // Windows - ExtractKeyWin(ss); -#endif + + if(demangled.empty()) + throw std::runtime_error("Demangle failed, structure may not be used as part of a key. Is your struct declared inside a function? Symbol=" + demangled); + + return ExtractKey(demangled); } diff --git a/src/autowiring/AutoConfigParser.hpp b/src/autowiring/AutoConfigParser.hpp index 8c830f248..c58183753 100644 --- a/src/autowiring/AutoConfigParser.hpp +++ b/src/autowiring/AutoConfigParser.hpp @@ -1,10 +1,9 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once -#include +#include #include TYPE_INDEX_HEADER namespace autowiring { - std::string ExtractKeyUnix(std::stringstream& ss); - std::string ExtractKeyWin(std::stringstream& ss); + std::string ExtractKey(const std::string& ss); std::string ExtractKey(const std::type_info& ti); } diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index dc9031e6c..c9134e3bc 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -106,7 +106,6 @@ set(Autowiring_SRCS EventRegistry.h ExceptionFilter.cpp ExceptionFilter.h - expect.hpp fast_pointer_cast.h GlobalCoreContext.cpp GlobalCoreContext.h diff --git a/src/autowiring/ContextEnumerator.cpp b/src/autowiring/ContextEnumerator.cpp index 87cf69c4b..13fc0c7ac 100644 --- a/src/autowiring/ContextEnumerator.cpp +++ b/src/autowiring/ContextEnumerator.cpp @@ -3,7 +3,11 @@ #include "ContextEnumerator.h" #include "CoreContext.h" -ContextEnumerator::ContextEnumerator(const std::shared_ptr& root): +ContextEnumerator::ContextEnumerator(void) : + m_root(CoreContext::CurrentContext()) +{} + +ContextEnumerator::ContextEnumerator(const std::shared_ptr& root) : m_root(root) {} @@ -46,6 +50,12 @@ const ContextEnumerator::iterator& ContextEnumerator::iterator::operator++(void) return *this; } +ContextEnumerator::iterator ContextEnumerator::iterator::operator++(int) { + auto retVal = *this; + _next(m_cur->FirstChild()); + return retVal; +} + CurrentContextEnumerator::CurrentContextEnumerator(void) : ContextEnumerator(CoreContext::CurrentContext()) { diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 2a27d24b6..6db150a79 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -245,14 +245,21 @@ void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { // the value, rather than the type passed in via traits.type, because the proper type might be a // concrete type defined in another context or potentially a unifier type. Creating a slot here // is also undesirable because the complete type is not available and we can't create a dynaimc - // caster to identify when this slot gets satisfied. + // caster to identify when this slot gets satisfied. If a slot was non-local, overwrite it. auto q = m_typeMemos.find(*traits.actual_type); if(q != m_typeMemos.end()) { auto& v = q->second; - if(*v.m_value == traits.pCoreObject) - throw std::runtime_error("An attempt was made to add the same value to the same context more than once"); - if(*v.m_value) - throw std::runtime_error("An attempt was made to add the same type to the same context more than once"); + + if (v.m_local) { + if (traits.pCoreObject && *v.m_value == traits.pCoreObject) + throw std::runtime_error("An attempt was made to add the same value to the same context more than once"); + if (*v.m_value) + throw std::runtime_error("An attempt was made to add the same type to the same context more than once"); + } + else { + v.m_value = traits.value; + v.m_local = true; + } } // Add the new concrete type: @@ -270,7 +277,7 @@ void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { // Notify any autowiring field that is currently waiting that we have a new member // to be considered. - UpdateDeferredElements(std::move(lk), m_concreteTypes.back()); + UpdateDeferredElements(std::move(lk), m_concreteTypes.back(), true); } // Moving this outside the lock because AddCoreRunnable will perform the checks inside its function @@ -320,19 +327,21 @@ void CoreContext::AddInternal(const AnySharedPointer& ptr) { UpdateDeferredElement(std::move(lk), entry); } -void CoreContext::FindByType(AnySharedPointer& reference) const { +void CoreContext::FindByType(AnySharedPointer& reference, bool localOnly) const { std::lock_guard lk(m_stateBlock->m_lock); - FindByTypeUnsafe(reference); + FindByTypeUnsafe(reference, localOnly); } -CoreContext::MemoEntry& CoreContext::FindByTypeUnsafe(AnySharedPointer& reference) const { +CoreContext::MemoEntry& CoreContext::FindByTypeUnsafe(AnySharedPointer& reference, bool localOnly) const { const std::type_info& type = reference->type(); // If we've attempted to search for this type before, we will return the value of the memo immediately: auto q = m_typeMemos.find(type); if(q != m_typeMemos.end()) { // We can copy over and return here - reference = q->second.m_value; + if (!localOnly || q->second.m_local){ + reference = q->second.m_value; + } return q->second; } @@ -504,57 +513,48 @@ void CoreContext::SignalShutdown(bool wait, ShutdownMode shutdownMode) { m_stateBlock->m_stateChanged.notify_all(); } + // Teardown interleave assurance--all of these contexts will generally be destroyed + // at the exit of this block, due to the behavior of SignalTerminate, unless exterior + // context references (IE, related to snooping) exist. + // + // This is done in order to provide a stable collection that may be traversed during + // teardown outside of a lock. + std::vector> childrenInterleave; + { - // Teardown interleave assurance--all of these contexts will generally be destroyed - // at the exit of this block, due to the behavior of SignalTerminate, unless exterior - // context references (IE, related to snooping) exist. - // - // This is done in order to provide a stable collection that may be traversed during - // teardown outside of a lock. - std::vector> childrenInterleave; + // Tear down all the children. + std::lock_guard lk(m_stateBlock->m_lock); - { - // Tear down all the children. - std::lock_guard lk(m_stateBlock->m_lock); + // Fill strong lock series in order to ensure proper teardown interleave: + childrenInterleave.reserve(m_children.size()); + for(const auto& entry : m_children) { + auto childContext = entry.lock(); + + // Technically, it *is* possible for this weak pointer to be expired, even though + // we're holding the lock. This may happen if the context itself is exiting even + // as we are processing SignalTerminate. In that case, the child context in + // question is blocking in its dtor lambda, waiting patiently until we're done, + // at which point it will modify the m_children collection. + if(!childContext) + continue; - // Fill strong lock series in order to ensure proper teardown interleave: - childrenInterleave.reserve(m_children.size()); - for(const auto& entry : m_children) { - auto childContext = entry.lock(); - - // Technically, it *is* possible for this weak pointer to be expired, even though - // we're holding the lock. This may happen if the context itself is exiting even - // as we are processing SignalTerminate. In that case, the child context in - // question is blocking in its dtor lambda, waiting patiently until we're done, - // at which point it will modify the m_children collection. - if(!childContext) - continue; - - // Add to the interleave so we can SignalTerminate in a controlled way. - childrenInterleave.push_back(childContext); - } + // Add to the interleave so we can SignalTerminate in a controlled way. + childrenInterleave.push_back(childContext); } - - // Now that we have a locked-down, immutable series, begin termination signalling: - for(size_t i = childrenInterleave.size(); i--; ) - childrenInterleave[i]->SignalShutdown(wait); } + // Now that we have a locked-down, immutable series, begin termination signalling: + for(size_t i = childrenInterleave.size(); i--; ) + childrenInterleave[i]->SignalShutdown(false, shutdownMode); + // Pass notice to all child threads: bool graceful = (shutdownMode == ShutdownMode::Graceful); for (auto itr = firstThreadToStop; itr != m_threads.end(); ++itr) (*itr)->Stop(graceful); - // Signal our condition variable - m_stateBlock->m_stateChanged.notify_all(); - - // Short-return if required: - if(!wait) - return; - - // Wait for the treads to finish before returning. - for (CoreRunnable* runnable : m_threads) - runnable->Wait(); + // Wait if requested + if(wait) + Wait(); } void CoreContext::Wait(void) { @@ -810,7 +810,7 @@ void CoreContext::UpdateDeferredElement(std::unique_lock&& lk, MemoE lk.unlock(); } -void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry) { +void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry, bool local) { { // Collection of items needing finalization: std::vector delayedFinalize; @@ -832,8 +832,8 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons for (auto& cur : m_typeMemos) { MemoEntry& value = cur.second; - if (value.m_value) - // This entry is already satisfied, no need to process it + if (value.m_value && value.m_local) + // This entry is already satisfied locally, no need to process it continue; // Determine whether the current candidate element satisfies the autowiring we are considering. @@ -845,6 +845,9 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons // Success, assign the traits value.pObjTraits = &entry; + // Store if it was injected from the local context or not + value.m_local = local; + // Now we need to take on the responsibility of satisfying this deferral. We will do this by // nullifying the flink, and by ensuring that the memo is satisfied at the point where we // release the lock. @@ -891,7 +894,8 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons lk.unlock(); ctxt->UpdateDeferredElements( std::unique_lock(ctxt->m_stateBlock->m_lock), - entry + entry, + false ); lk.lock(); } diff --git a/src/autowiring/JunctionBoxManager.cpp b/src/autowiring/JunctionBoxManager.cpp index 7ed8fc499..db96e3f40 100644 --- a/src/autowiring/JunctionBoxManager.cpp +++ b/src/autowiring/JunctionBoxManager.cpp @@ -26,7 +26,9 @@ JunctionBoxManager::~JunctionBoxManager(void) {} std::shared_ptr JunctionBoxManager::Get(const std::type_index& pTypeIndex) { auto box = m_junctionBoxes.find(pTypeIndex); - assert(box != m_junctionBoxes.end() && "If JunctionBox isn't found, EventRegistry isn't working"); + if (box == m_junctionBoxes.end()) + throw autowiring_error("Attempted to obtain a junction box for an unregistered type"); + return box->second; } diff --git a/src/autowiring/SlotInformation.cpp b/src/autowiring/SlotInformation.cpp index a6b069df4..9fc6671fa 100644 --- a/src/autowiring/SlotInformation.cpp +++ b/src/autowiring/SlotInformation.cpp @@ -10,12 +10,12 @@ static autowiring::thread_specific_ptr tss([](SlotInformationStackLocation*) {}); SlotInformationStackLocation::SlotInformationStackLocation(SlotInformationStumpBase& stump, const void* pObj, size_t extent) : - prior(*tss), stump(stump), - m_pCur(nullptr), - m_pLastLink(nullptr), pObj(pObj), - extent(extent) + extent(extent), + prior(*tss), + m_pCur(nullptr), + m_pLastLink(nullptr) { tss.reset(this); } diff --git a/src/autowiring/demangle.cpp b/src/autowiring/demangle.cpp index 6964a3cb3..7c2ca6fa8 100644 --- a/src/autowiring/demangle.cpp +++ b/src/autowiring/demangle.cpp @@ -13,13 +13,22 @@ std::string autowiring::demangle(const std::type_info& ti) { abi::__cxa_demangle(ti.name(), nullptr, nullptr, &status), std::free }; - return std::string(status == 0 ? res.get() : ti.name()); + + if(status != 0) + return std::string(); + + return std::string(res.get()); } #else // Windows std::string autowiring::demangle(const std::type_info& ti) { - return std::string(ti.name()); + std::string demangled(ti.name()); + + if (demangled.find("`") != std::string::npos) + return std::string(); + + return demangled; } #endif diff --git a/src/autowiring/expect.hpp b/src/autowiring/expect.hpp deleted file mode 100644 index 2d177bdca..000000000 --- a/src/autowiring/expect.hpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. -#pragma once -#include - -namespace autowiring { -/// -/// Expectation type, used in utility processing -/// -template -class _expect { -public: - typedef std::basic_istream> stream; - - _expect(const ch* expected) : - m_expected(expected) - { - } - -private: - const ch* m_expected; - -public: - stream& operator()(stream& lhs) const { - ch c; - for(size_t i = 0; m_expected[i] && (lhs >> c); i++) - if(toupper(m_expected[i]) != toupper(c)) - lhs.setstate(std::ios::failbit); - return lhs; - } -}; - -template -static typename _expect::stream& operator>>(typename _expect::stream& lhs, const _expect& rhs) { - return rhs(lhs); -} - -template -_expect expect(const ch* str) { - return _expect(str); -} -} diff --git a/src/autowiring/test/AutoConfigTest.cpp b/src/autowiring/test/AutoConfigTest.cpp index 761b7e089..d50476c15 100644 --- a/src/autowiring/test/AutoConfigTest.cpp +++ b/src/autowiring/test/AutoConfigTest.cpp @@ -8,6 +8,50 @@ class AutoConfigTest: public testing::Test {}; +TEST_F(AutoConfigTest, ExtractKeyTestWin) { + std::string type1("struct ConfigTypeExtractor"); + const auto key1 = autowiring::ExtractKey(type1); + ASSERT_EQ("Namespace1.XYZ", key1) << "Failed to properly extract Key with a single namespace"; + + std::string type2("struct ConfigTypeExtractor"); + const auto key2 = autowiring::ExtractKey(type2); + ASSERT_EQ("Namespace1.Namespace2.XYZ", key2) << "Failed to properly extract Key with multiple namespaces"; + + std::string type3("struct ConfigTypeExtractor"); + const auto key3 = autowiring::ExtractKey(type3); + ASSERT_EQ("XYZ", key3) << "Failed to properly extract Key with no namespace"; + + std::string type4("struct ConfigTypeExtractor"); + const auto key4 = autowiring::ExtractKey(type4); + ASSERT_EQ("ClassNamespace1.AutoConfigTest.XYZ", key4) << "Failed to properly extract Key with class namespaces"; + + std::string type5("struct ConfigTypeExtractor"); + const auto key5 = autowiring::ExtractKey(type5); + ASSERT_EQ("ClassNamespace1.Base::Nested.XYZ", key5) << "Failed to properly extract Key from nested"; +} + +TEST_F(AutoConfigTest, ExtractKeyTestPOSIX) { + std::string type1("ConfigTypeExtractor"); + const auto key1 = autowiring::ExtractKey(type1); + ASSERT_EQ("Namespace1.XYZ", key1) << "Failed to properly extract Key with a single namespace"; + + std::string type2("ConfigTypeExtractor"); + const auto key2 = autowiring::ExtractKey(type2); + ASSERT_EQ("Namespace1.Namespace2.XYZ", key2) << "Failed to properly extract Key with multiple namespaces"; + + std::string type3("ConfigTypeExtractor"); + const auto key3 = autowiring::ExtractKey(type3); + ASSERT_EQ("XYZ", key3) << "Failed to properly extract Key with no namespace"; + + std::string type4("ConfigTypeExtractor"); + const auto key4 = autowiring::ExtractKey(type4); + ASSERT_EQ("ClassNamespace1.AutoConfigTest.XYZ", key4) << "Failed to properly extract Key with class namespaces"; + + std::string type5("ConfigTypeExtractor"); + const auto key5 = autowiring::ExtractKey(type5); + ASSERT_EQ("ClassNamespace1.Base::Nested.XYZ", key5) << "Failed to properly extract Key from nested"; +} + struct MyConfigurableClass { AutoConfig m_myName; }; @@ -33,6 +77,17 @@ TEST_F(AutoConfigTest, VerifySimpleAssignment) { ASSERT_EQ(323, *mcc->m_myName) << "Configurable type did not receive a value as expected"; } +struct NamespaceRoot; +struct NamespaceChild; + +TEST_F(AutoConfigTest, VerifyNestedNamespace) { + AutoRequired acm; + acm->Set("NamespaceRoot.NamespaceChild.Namespace1.Namespace2.XYZ", 142); + + AutoConfig cfg; + ASSERT_EQ(142, *cfg); +} + struct MyBoolClass { AutoConfig m_bool; }; @@ -120,15 +175,6 @@ TEST_F(AutoConfigTest, VerifyDuplicateConfigAssignment) { ASSERT_EQ(1111, *clz2->m_myName); } -TEST_F(AutoConfigTest, ExtractKeyTestWin) { - std::stringstream win("struct ConfigTypeExtractor"); - - ASSERT_STREQ( - "Namespace1.XYZ", - autowiring::ExtractKeyWin(win).c_str() - ) << "Windows key extraction implementation mismatch"; -} - class TypeWithoutAShiftOperator { public: int foo; @@ -180,14 +226,18 @@ TEST_F(AutoConfigTest, NestedContexts) { std::shared_ptr ctxt_middle = ctxt_outer->Create(); std::shared_ptr ctxt_sibling = ctxt_outer->Create(); std::shared_ptr ctxt_inner = ctxt_middle->Create(); + std::shared_ptr ctxt_no_manager = ctxt_inner->Create(); + std::shared_ptr ctxt_leaf = ctxt_no_manager->Create(); AutoRequired mcc_outer(ctxt_outer); AutoRequired mcc_middle(ctxt_middle); AutoRequired mcc_sibling(ctxt_sibling); AutoRequired mcc_inner(ctxt_inner); + AutoRequired mcc_leaf(ctxt_leaf); AutoRequired acm_outer(ctxt_outer); AutoRequired acm_middle(ctxt_middle); + AutoRequired acm_leaf(ctxt_leaf); // Set initial value acm_outer->Set("Namespace1.XYZ", 42); @@ -195,7 +245,9 @@ TEST_F(AutoConfigTest, NestedContexts) { ASSERT_EQ(42, *mcc_middle->m_myName) << "Config value not set in descendant context"; ASSERT_EQ(42, *mcc_sibling->m_myName) << "Config value not set in descendant context"; ASSERT_EQ(42, *mcc_inner->m_myName) << "Config value not set in descendant context"; + ASSERT_EQ(42, *mcc_leaf->m_myName) << "Config value not set in desendant context"; EXPECT_TRUE(acm_middle->IsInherited("Namespace1.XYZ")) << "Inherited key not marked as such"; + EXPECT_TRUE(acm_leaf->IsInherited("Namespace1.XYZ")) << "Inherited key not marked as such"; // Set middle, inner shouldn't be able to be set from outer after this bool callback_hit1 = false; @@ -207,6 +259,7 @@ TEST_F(AutoConfigTest, NestedContexts) { ASSERT_EQ(42, *mcc_sibling->m_myName) << "Config value set from sibling context"; ASSERT_EQ(1337, *mcc_middle->m_myName) << "Config value not set"; ASSERT_EQ(1337, *mcc_inner->m_myName) << "Config value not set in child context"; + ASSERT_EQ(1337, *mcc_leaf->m_myName) << "Config value not set in leaf context"; ASSERT_TRUE(callback_hit1) << "Callback not hit in inner context"; // Set from outter, inner should be shielded by middle context @@ -236,6 +289,7 @@ struct ValidatedKey{ return value > 5; } }; + struct MyValidatedClass{ AutoConfig m_config; }; @@ -256,3 +310,105 @@ TEST_F(AutoConfigTest, Validators) { acm->Set("ValidatedKey", 1337); ASSERT_EQ(1337, *valid->m_config) << "Value not set for key"; } + +TEST_F(AutoConfigTest, DirectAssignemnt) { + AutoConfig var; + var = 10; + ASSERT_EQ(10, *var); + + AutoRequired containsVar; + + ASSERT_EQ(10, *var); + ASSERT_EQ(10, *containsVar->m_myName); +} + + +struct ComplexValue { + int a; + int b; + int c; + + ComplexValue(int nA, int nB, int nC) : a(nA), b(nB), c(nC) {} + ComplexValue(int repeated) : a(repeated), b(repeated), c(repeated) {} +}; + +struct MyComplexValueClass { + AutoConfig m_cfg; + AutoConfig m_cfg2 = ComplexValue{ 10, 15, 30 }; + + MyComplexValueClass() : m_cfg(ComplexValue{ 2, 20, 20 }) {} +}; + +TEST_F(AutoConfigTest, ComplexConstruction){ + AutoRequired mgr; + ASSERT_FALSE(mgr->IsConfigured("Namespace1.MyCxValue")); + + AutoConfig defaultConstructed; + + ASSERT_FALSE(mgr->IsConfigured("Namespace1.MyCxValue")) << "Improperly set config value when default constructing AutoConfig"; + + AutoConfig fancyConstructed(1, 2, 3); + + ASSERT_TRUE(mgr->IsConfigured("Namespace1.MyCxValue")) << "Initializing constructor did not set config value"; + ASSERT_EQ(fancyConstructed->a, 1) << "Initializing constructor did not set config value"; + ASSERT_EQ(fancyConstructed->b, 2) << "Initializing constructor did not set config value"; + ASSERT_EQ(fancyConstructed->c, 3) << "Initializing constructor did not set config value"; + + + AutoConfig fancy2(7); + ASSERT_EQ(fancy2->a, 1) << "Second Initalizing constructor overrode the first!"; + ASSERT_EQ(fancy2->b, 2) << "Second Initalizing constructor overrode the first!"; + ASSERT_EQ(fancy2->c, 3) << "Second Initalizing constructor overrode the first!"; +} + +struct OuterCtxt{}; +struct MiddleCtxt{}; +struct InnerCtxt{}; +TEST_F(AutoConfigTest, ListingConfigs) { + AutoCreateContextT ctxt_outer; + auto ctxt_middle = ctxt_outer->Create(); + auto ctxt_inner = ctxt_middle->Create(); + + AutoRequired acm_outer(ctxt_outer); + AutoRequired acm_inner(ctxt_inner); + + AutoRequired var1_inner(ctxt_inner); + var1_inner->m_myName = 1; + + ASSERT_EQ(0, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; + ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; + + int callback_outer = 0; + acm_outer->AddCallback([&callback_outer](const std::string& key, const AnySharedPointer& ptr) { + ++callback_outer; + }); + + int callback_inner = 0; + acm_inner->AddCallback([&callback_inner](const std::string& key, const AnySharedPointer& ptr) { + ++callback_inner; + }); + + ASSERT_EQ(1, callback_inner) << "Callback not called on existing keys"; + + AutoRequired var1_outer(ctxt_outer); + var1_outer->m_myName = 2; + + ASSERT_EQ(1, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; + ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; + + AutoRequired var2_outer(ctxt_outer); + var2_outer->m_myName = 3; + + ASSERT_EQ(2, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; + ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; + + ASSERT_EQ(2, callback_outer) << "Outer callback called an incorrect number of times"; + ASSERT_EQ(1, callback_inner) << "Inner callback called an incorrect number of times"; + + auto keys_outer = acm_outer->GetLocalKeys(); + ASSERT_EQ(var1_outer->m_myName.m_key, keys_outer[0]) << "Keys listed out of construction order"; + ASSERT_EQ(var2_outer->m_myName.m_key, keys_outer[1]) << "Keys listed out of construction order"; + + auto keys_inner = acm_inner->GetLocalKeys(); + ASSERT_EQ(var1_inner->m_myName.m_key, keys_inner[0]) << "Keys listed out of construction order"; +} diff --git a/src/autowiring/test/ContextEnumeratorTest.cpp b/src/autowiring/test/ContextEnumeratorTest.cpp index 88b75ba0c..9cc7b5feb 100644 --- a/src/autowiring/test/ContextEnumeratorTest.cpp +++ b/src/autowiring/test/ContextEnumeratorTest.cpp @@ -11,7 +11,7 @@ class ContextEnumeratorTest: TEST_F(ContextEnumeratorTest, DegenerateEnumeration) { size_t ct = 0; for(const auto& cur : ContextEnumerator(std::shared_ptr(nullptr))) { - ASSERT_TRUE(!!cur.get()) << "Context enumerator incorrectly enumerated a null context pointer"; + ASSERT_TRUE(!!cur) << "Context enumerator incorrectly enumerated a null context pointer"; ct++; } ASSERT_EQ(0UL, ct) << "An empty enumerator unexpectedly enumerated one or more entries"; @@ -172,3 +172,55 @@ TEST_F(ContextEnumeratorTest, ComplexRemovalInterference) { ASSERT_TRUE(cur.expired()) << "Found an element that was iterated after it should have been unreachable"; } +TEST_F(ContextEnumeratorTest, Unique) { + AutoCreateContextT named; + ASSERT_EQ(named, ContextEnumeratorT().unique()) << + "Expected the unique context to be equal to the specifically named child context"; +} + +TEST_F(ContextEnumeratorTest, BadUnique) { + AutoCreateContextT named1; + AutoCreateContextT named2; + + ASSERT_THROW(ContextEnumeratorT().unique(), autowiring_error) << + "An attempt to obtain a unique context from an enumerator providing more than one should throw an exception"; +} + +TEST_F(ContextEnumeratorTest, ForwardIteratorCheck) { + static_assert(std::is_default_constructible::value, "ForwardIterator constraint requires iterators to be default-constructable"); + static_assert( + std::is_same< + std::iterator_traits::iterator_category, + std::forward_iterator_tag + >::value, + "ContextEnumerator iterator not recognized as a forward iterator" + ); + + AutoCreateContext ctxt1; + AutoCreateContext ctxt2; + AutoCreateContext ctxt2_0(ctxt2); + AutoCreateContext ctxt2_1(ctxt2); + AutoCreateContext ctxt2_1_0(ctxt2_1); + AutoCreateContext ctxt3; + + ContextEnumerator e; + auto a = e.begin(); + auto b = e.begin(); + + ASSERT_EQ(a++, b++) << "Incrementation equivalence was not satisfied"; + ASSERT_EQ(a, b) << "Iterators not equivalent after incrementation"; + + auto prior = *b; + a++; + ASSERT_EQ(prior, *b) << "Incrementation of an unrelated iterator invalidated a copy of that iterator"; + ++b; + ASSERT_EQ(a, b) << "Postfix and prefix incrementation are not equivalently implemented"; + + a++; + ASSERT_EQ(a, std::next(b)) << "std::next did not correctly return the next iterator after the specified iterator"; + b++; + + std::advance(a, 1); + b++; + ASSERT_EQ(a, b) << "std::advance did not actually advance an iterator"; +} diff --git a/src/autowiring/test/ContextMapTest.cpp b/src/autowiring/test/ContextMapTest.cpp index cc3bc0014..268587e93 100644 --- a/src/autowiring/test/ContextMapTest.cpp +++ b/src/autowiring/test/ContextMapTest.cpp @@ -237,3 +237,35 @@ TEST_F(ContextMapTest, VerifySimpleEnumeration) { EXPECT_EQ(1UL, found.count("context_se_3")) << "Failed to find map element '3'"; } +TEST_F(ContextMapTest, VerifyRangeBasedEnumeration) { + std::shared_ptr ctxt1 = AutoCreateContext(); + std::shared_ptr ctxt2 = AutoCreateContext(); + std::shared_ptr ctxt3 = AutoCreateContext(); + + ContextMap mp; + mp.Add("a", ctxt1); + mp.Add("b", ctxt2); + mp.Add("c", ctxt3); + + size_t ct = 0; + + // Internal map in ContextMap ensures entries are enumerated in-order + for (auto& cur : mp) { + switch (ct) { + case 0: + // Release the entry we are presently enumerating + ctxt1.reset(); + break; + case 1: + // No behavior + break; + case 2: + // Release an entry we already enumerated + ctxt2.reset(); + break; + } + ct++; + } + + ASSERT_EQ(3UL, ct) << "Context map range-based enumeration did not correctly enumerate all members"; +} \ No newline at end of file diff --git a/src/autowiring/test/CoreContextTest.cpp b/src/autowiring/test/CoreContextTest.cpp index 779257299..fada6dfec 100644 --- a/src/autowiring/test/CoreContextTest.cpp +++ b/src/autowiring/test/CoreContextTest.cpp @@ -360,4 +360,77 @@ TEST_F(CoreContextTest, CoreContextAdd) { Autowired mc; ASSERT_TRUE(mc.IsAutowired()) << "Manually registered interface was not detected as expected"; -} \ No newline at end of file +} + +struct ExplicitlyHoldsOutstandingCount: + public CoreRunnable +{ +public: + bool OnStart(void) override { + outstanding = CoreRunnable::GetOutstanding(); + return true; + } + + void OnStop(bool) override { + // Just mark that this event took place + calledStop.set_value(true); + } + + void DoAdditionalWait(void) override { + std::unique_lock lk(lock); + cv.wait(lk, [&] { return !outstanding; }); + } + + void Proceed(void) { + std::lock_guard lk(lock); + outstanding.reset(); + cv.notify_all(); + } + + std::promise calledStop; + + std::mutex lock; + std::condition_variable cv; + std::shared_ptr outstanding; +}; + +TEST_F(CoreContextTest, AppropriateShutdownInterleave) { + // Need both an outer and an inner context + AutoCurrentContext ctxtOuter; + AutoCreateContext ctxtInner; + + // Need to inject types at both scopes + AutoRequired outer(ctxtOuter); + AutoRequired inner(ctxtInner); + + // Start both contexts up + ctxtOuter->Initiate(); + ctxtInner->Initiate(); + + // Now shut down the outer context. Hand off to an async, we want this to block. + std::future holder = std::async( + std::launch::async, + [&] { + ctxtOuter->SignalShutdown(true); + } + ); + + // Need to ensure that both outstanding counters are reset at some point: + { + auto cleanup = MakeAtExit([&] { + outer->Proceed(); + inner->Proceed(); + }); + + // Outer entry should have called "stop": + auto future = outer->calledStop.get_future(); + ASSERT_EQ( + std::future_status::ready, + future.wait_for(std::chrono::seconds(5)) + ) << "Outer scope's OnStop method was incorrectly blocked by a child context member taking a long time to shut down"; + } + + // Both contexts should be stopped now: + ASSERT_TRUE(ctxtOuter->Wait(std::chrono::seconds(5))) << "Outer context did not tear down in a timely fashion"; + ASSERT_TRUE(ctxtInner->Wait(std::chrono::seconds(5))) << "Inner context did not tear down in a timely fashion"; +} diff --git a/src/autowiring/test/ScopeTest.cpp b/src/autowiring/test/ScopeTest.cpp index 498095650..d062f4a82 100644 --- a/src/autowiring/test/ScopeTest.cpp +++ b/src/autowiring/test/ScopeTest.cpp @@ -170,3 +170,109 @@ TEST_F(ScopeTest, AutowiringOrdering) { } } +class InnerContext {}; +class MiddleContext {}; +TEST_F(ScopeTest, RequireVsWire) { + AutoCurrentContext ctxt_outer; + auto ctxt_middle = ctxt_outer->Create(); + auto ctxt_inner = ctxt_middle->Create(); + + CurrentContextPusher pshr(ctxt_inner); + + Autowired a_wired_inner; + ASSERT_FALSE(a_wired_inner.IsAutowired()) << "Autowired member became autowired too quickly"; + ASSERT_EQ(a_wired_inner.GetContext(), ctxt_inner) << "Autowired member created in the wrong context"; + + AutoRequired a_required_outer(ctxt_outer); + ASSERT_TRUE(a_required_outer.IsAutowired()) << "AutoRequired member unsatisfied after construction"; + ASSERT_EQ(a_required_outer->GetContext(), ctxt_outer) << "AutoRequired member satisfied in the wrong context"; + + ASSERT_TRUE(a_wired_inner.IsAutowired()) << "AutoRequired member in parent did not satisfy Autowired member in child"; + ASSERT_EQ(a_wired_inner.get(), a_required_outer.get()) << "Mismatch between Autowired and Autorequired members"; + + //this overrides the slot in the middle context; + AutoRequired a_required_middle(ctxt_middle); + ASSERT_TRUE(a_required_middle.IsAutowired()) << "AutoRequired member not satisfied!"; + ASSERT_EQ(a_required_middle->GetContext(), ctxt_middle) << "AutoRequired member not created in child context"; + ASSERT_NE(a_required_middle, a_required_outer) << "AutoRequired member not constructed in child context"; + + Autowired a_wired_inner2(ctxt_inner); + ASSERT_TRUE(a_wired_inner2.IsAutowired()); + ASSERT_EQ(a_wired_inner2.get(), a_required_middle.get()) << "Autowired member did not redirect to the closest context"; + ASSERT_EQ(a_wired_inner.get(), a_required_outer.get()) << "Autowired object changed after satisfaction!"; + + AutoRequired b_required_middle(ctxt_middle); + AutoRequired b_required_outer(ctxt_outer); + Autowired b_wired_inner; + ASSERT_TRUE(b_wired_inner.IsAutowired()) << "Autowired failed to find member"; + ASSERT_EQ(b_wired_inner.get(), b_required_middle.get()) << "Autorequiring overwrote a slot it shouldn't have!"; +} + +TEST_F(ScopeTest, RequireVsWireFast) { + AutoCurrentContext ctxt_outer; + auto ctxt_middle = ctxt_outer->Create(); + auto ctxt_inner = ctxt_middle->Create(); + + CurrentContextPusher pshr(ctxt_inner); + + //This adds a memo for this type to the inner context + AutowiredFast a_wired(ctxt_inner); + ASSERT_FALSE(a_wired.IsAutowired()) << "Autowired member became autowired too quickly"; + + //This satisfies the memo in the inner context + AutoRequired a_required_outer(ctxt_outer); + ASSERT_TRUE(a_required_outer.IsAutowired()) << "AutoRequired member unsatisfied after construction"; + ASSERT_EQ(a_required_outer->GetContext(), ctxt_outer) << "AutoRequired member satisfied in the wrong context"; + + ASSERT_FALSE(a_wired.IsAutowired()) << "AutowiredFast was satisfied after construction!"; + + //This overrides the memo in the middle context. + AutoRequired a_required_middle(ctxt_middle); + ASSERT_TRUE(a_required_middle.IsAutowired()) << "AutoRequired member not satisfied!"; + ASSERT_EQ(a_required_middle->GetContext(), ctxt_middle) << "AutoRequired member not created in child context"; + ASSERT_NE(a_required_middle.get(), a_required_outer.get()) << "AutoRequired member not constructed in child context"; + + //This should direct to the middle context + AutowiredFast a_wired2(ctxt_inner); + ASSERT_TRUE(a_wired2.IsAutowired()); + ASSERT_EQ(a_wired2, a_required_middle) << "Autowired member did not grab the most derived object"; +} + +class ParentInjector : public ContextMember { +public: + ParentInjector(int v, bool inject = false) : ContextMember("Parent Injector"), value(v) { + if (!inject) { + return; + } + + const auto ctxt = m_context.lock(); + if (!ctxt) { + return; + } + + auto parent = ctxt->GetParentContext(); + if (!parent) { + return; + } + + parent->Inject(9000, false); + } + int value; +}; + +TEST_F(ScopeTest, RecursiveInject) { + AutoCurrentContext ctxt_outer; + auto ctxt_inner = ctxt_outer->Create(); + + CurrentContextPusher pshr(ctxt_inner); + + AutoRequired a_inner(ctxt_inner, 1, true); + + ASSERT_EQ(a_inner->GetContext(), ctxt_inner); + ASSERT_EQ(a_inner->value, 1); + + AutoRequired a_inner_2(ctxt_inner, 2); + + ASSERT_EQ(a_inner, a_inner_2); + ASSERT_EQ(a_inner->GetContext(), a_inner_2->GetContext()); +} \ No newline at end of file diff --git a/version.cmake b/version.cmake index 006a6503f..fbbe55b7b 100644 --- a/version.cmake +++ b/version.cmake @@ -1 +1 @@ -set(autowiring_VERSION 0.5.0) +set(autowiring_VERSION 0.5.1)