Merge lp:~michihenning/unity-scopes-api/loop-prevention into lp:unity-scopes-api

Proposed by Michi Henning
Status: Merged
Approved by: Marcus Tomlinson
Approved revision: 285
Merged at revision: 291
Proposed branch: lp:~michihenning/unity-scopes-api/loop-prevention
Merge into: lp:unity-scopes-api
Diff against target: 1638 lines (+826/-169)
43 files modified
CMakeLists.txt (+1/-1)
RELEASE_NOTES.md (+5/-0)
STRUCTS (+4/-4)
debian/changelog (+7/-0)
debian/libunity-scopes3.symbols (+5/-3)
include/unity/scopes/Category.h (+1/-1)
include/unity/scopes/QueryBase.h (+2/-2)
include/unity/scopes/QueryMetadata.h (+3/-0)
include/unity/scopes/SearchMetadata.h (+2/-1)
include/unity/scopes/SearchQueryBase.h (+4/-4)
include/unity/scopes/internal/MWScope.h (+19/-4)
include/unity/scopes/internal/ScopeImpl.h (+15/-4)
include/unity/scopes/internal/ScopeObject.h (+7/-6)
include/unity/scopes/internal/ScopeObjectBase.h (+1/-0)
include/unity/scopes/internal/SearchQueryBaseImpl.h (+16/-15)
include/unity/scopes/internal/smartscopes/SSScopeObject.h (+4/-3)
include/unity/scopes/internal/zmq_middleware/ZmqScope.h (+3/-2)
include/unity/scopes/utility/internal/BufferedResultForwarderImpl.h (+2/-2)
include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h (+1/-1)
src/scopes/SearchQueryBase.cpp (+3/-3)
src/scopes/internal/JsonCppNode.cpp (+5/-0)
src/scopes/internal/JsonSettingsSchema.cpp (+5/-1)
src/scopes/internal/QueryCtrlImpl.cpp (+8/-7)
src/scopes/internal/ScopeImpl.cpp (+45/-9)
src/scopes/internal/ScopeObject.cpp (+34/-1)
src/scopes/internal/SearchMetadataImpl.cpp (+2/-2)
src/scopes/internal/SearchQueryBaseImpl.cpp (+90/-73)
src/scopes/internal/smartscopes/SSScopeObject.cpp (+4/-3)
src/scopes/internal/zmq_middleware/ScopeI.cpp (+2/-0)
src/scopes/internal/zmq_middleware/ZmqScope.cpp (+6/-1)
src/scopes/internal/zmq_middleware/capnproto/Scope.capnp (+2/-12)
test/gtest/scopes/CMakeLists.txt (+1/-0)
test/gtest/scopes/LoopDetection/CMakeLists.txt (+26/-0)
test/gtest/scopes/LoopDetection/LoopDetection_test.cpp (+249/-0)
test/gtest/scopes/LoopDetection/LoopScope.cpp (+177/-0)
test/gtest/scopes/LoopDetection/LoopScope.h (+41/-0)
test/gtest/scopes/LoopDetection/LoopScope.ini.in (+4/-0)
test/gtest/scopes/LoopDetection/Runtime.ini.in (+6/-0)
test/gtest/scopes/LoopDetection/TestRegistry.ini.in (+7/-0)
test/gtest/scopes/LoopDetection/Zmq.ini.in (+2/-0)
test/gtest/scopes/internal/JsonSettingsSchema/JsonSettingsSchema_test.cpp (+2/-2)
test/gtest/scopes/internal/zmq_middleware/ZmqMiddleware/ZmqMiddleware_test.cpp (+1/-0)
test/gtest/scopes/testing/IsolatedScope/IsolatedScope_test.cpp (+2/-2)
To merge this branch: bzr merge lp:~michihenning/unity-scopes-api/loop-prevention
Reviewer Review Type Date Requested Status
Marcus Tomlinson (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+244830@code.launchpad.net

Commit message

Added loop detection for subsearch(). If an aggregation loop is detected, the current subquery immediately finishes with a status of OK, but a message saying that a loop was found. This returns an empty results set for the subquery() that detected the loop to the client, while leaving other subqueries alone.

Description of the change

Added loop detection for subsearch(). If an aggregation loop is detected, the current subquery immediately finishes with a status of OK, but a message saying that a loop was found. This returns an empty results set for the subquery() that detected the loop to the client, while leaving other subqueries alone.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

Yeah, OK, OK, I give in… :-)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

Putting this on hold until we get the go-ahead for an ABI_breaking change.

Revision history for this message
Michi Henning (michihenning) wrote :

Got carried away with this. After discussing with Marcus, we can let this in, seeing that it doesn't break the API (only the child scopes changes do).

Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Sorry it took me so long to get around to reviewing this. This looks really cool! Just one small concern:

714 QueryCtrlProxy SearchQueryBaseImpl::subsearch(ScopeProxy const& scope,

I'm seeing a lot of duplicated code for the subsearch() variants. Could we rather just implement the one with the most arguments, and simply forward the other subsearch() calls to it? This will also give us some slightly better test coverage :)

review: Needs Fixing
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

1136 + string completion_msg() const

I also just noticed that this method is never used, hence the value of completion_msg_ is never checked. Was this an oversight?

282. By Michi Henning

Removed unused test code.

Revision history for this message
Michi Henning (michihenning) wrote :

> 1136 + string completion_msg() const
>
> I also just noticed that this method is never used, hence the value of
> completion_msg_ is never checked. Was this an oversight?

Sort of: I modified a different test and forgot to remove this. It's gone now.

The message is seen only by the aggregator whose sub-query caused the loop so, for aggregators earlier in the chain, the query finishes normally with an empty message. Hence, I can't make the message "bubble all the way back" to the top-level client. But the log message should be sufficient, I'd say. Those loops are never meant to happen in the first place, so I think that's good enough.

283. By Michi Henning

Re-factored subsearch().

284. By Michi Henning

Cosmetic change.

Revision history for this message
Michi Henning (michihenning) wrote :

> Sorry it took me so long to get around to reviewing this.

Thanks for taking the time!

> Just one small concern:
>
> 714 QueryCtrlProxy SearchQueryBaseImpl::subsearch(ScopeProxy const&
> scope,
>
> I'm seeing a lot of duplicated code for the subsearch() variants.

I was pissed off about that too when I wrote this. I originally factored all these out into common error checking and the do_subsearch() with a lambda. Then, during testing, I realised that we have the corner case for the mock testing scope, and I ended up with two lambdas for each overload, all of them subtly different.

What didn't occur to me at the time was that the overloads can be funnelled into a single method in the implementation of the public API rather than blindly forwarding the public overloads to corresponding internal ones.

I re-factored accordingly now, and it's amazing how much simpler things fell out!

Thanks for making me look at this again! I like deleting code. The best code there is is no code :-)

285. By Michi Henning

Merged Marcus's JsonSettingsSchema test fix.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-12-09 15:26:12 +0000
3+++ CMakeLists.txt 2015-01-16 00:25:35 +0000
4@@ -207,7 +207,7 @@
5 # API version
6 set(UNITY_SCOPES_MAJOR 0)
7 set(UNITY_SCOPES_MINOR 6)
8-set(UNITY_SCOPES_MICRO 10)
9+set(UNITY_SCOPES_MICRO 11)
10 set(UNITY_SCOPES_SOVERSION 3)
11
12 # Version for testing, with all symbols visible
13
14=== modified file 'RELEASE_NOTES.md'
15--- RELEASE_NOTES.md 2014-12-09 15:51:02 +0000
16+++ RELEASE_NOTES.md 2015-01-16 00:25:35 +0000
17@@ -1,6 +1,11 @@
18 Release notes
19 =============
20
21+Changes in version 0.6.11
22+=========================
23+ - Prevent query from looping indefinitely if a query is forwarded
24+ among aggregators and loops back to an earlier aggregator.
25+
26 Changes in version 0.6.10
27 =========================
28 - Renamed "Tags" scope .ini option to "Keywords".
29
30=== modified file 'STRUCTS'
31--- STRUCTS 2014-12-03 08:05:20 +0000
32+++ STRUCTS 2015-01-16 00:25:35 +0000
33@@ -161,10 +161,10 @@
34
35 QueryMetadata (returned by serialize())
36 =======================================
37- 'type' : string (metadata type)
38- 'locale' : string
39- 'form_factor' : string
40- 'hints' : dict
41+ 'type' : string (metadata type)
42+ 'locale' : string
43+ 'form_factor' : string
44+ 'hints' : dict
45 'internet_connectivity' : bool (optional)
46
47 SearchMetadata (returned by serialize())
48
49=== modified file 'debian/changelog'
50--- debian/changelog 2014-12-12 11:48:26 +0000
51+++ debian/changelog 2015-01-16 00:25:35 +0000
52@@ -1,3 +1,10 @@
53+unity-scopes-api (0.6.11+15.04.20141212-0ubuntu2) UNRELEASED; urgency=medium
54+
55+ * Prevent query from looping indefinitely if a query is forwarded
56+ among aggregators and loops back to an earlier aggregator.
57+
58+ -- Michi Henning <michi.henning@canonical.com> Wed, 17 Dec 2014 10:39:43 +1000
59+
60 unity-scopes-api (0.6.10+15.04.20141212-0ubuntu1) vivid; urgency=low
61
62 [ Pawel Stolowski ]
63
64=== modified file 'debian/libunity-scopes3.symbols'
65--- debian/libunity-scopes3.symbols 2014-12-10 12:59:46 +0000
66+++ debian/libunity-scopes3.symbols 2015-01-16 00:25:35 +0000
67@@ -466,12 +466,14 @@
68 (c++)"unity::scopes::internal::ScopeImpl::fwd()@Base" 0.4.3+14.10.20140428
69 (c++)"unity::scopes::internal::ScopeImpl::perform_action(unity::scopes::Result const&, unity::scopes::ActionMetadata const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::shared_ptr<unity::scopes::ActivationListenerBase> const&)@Base" 0.4.0+14.04.20140312.1
70 (c++)"unity::scopes::internal::ScopeImpl::preview(unity::scopes::Result const&, unity::scopes::ActionMetadata const&, std::shared_ptr<unity::scopes::PreviewListenerBase> const&)@Base" 0.4.0+14.04.20140312.1
71+ (c++)"unity::scopes::internal::ScopeImpl::runtime() const@Base" 0replaceme
72 (c++)"unity::scopes::internal::ScopeImpl::~ScopeImpl()@Base" 0.4.0+14.04.20140312.1
73 (c++)"unity::scopes::internal::ScopeImpl::ScopeImpl(std::shared_ptr<unity::scopes::internal::MWScope> const&, unity::scopes::internal::RuntimeImpl*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.4.0+14.04.20140312.1
74 (c++)"unity::scopes::internal::ScopeImpl::search(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unity::scopes::FilterState const&, unity::scopes::SearchMetadata const&, std::shared_ptr<unity::scopes::SearchListenerBase> const&)@Base" 0.4.0+14.04.20140312.1
75+ (c++)"unity::scopes::internal::ScopeImpl::search(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unity::scopes::FilterState const&, unity::scopes::SearchMetadata const&, std::vector<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > const&, std::shared_ptr<unity::scopes::SearchListenerBase> const&)@Base" 0replaceme
76 (c++)"unity::scopes::internal::ScopeImpl::search(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unity::scopes::FilterState const&, unity::scopes::SearchMetadata const&, std::shared_ptr<unity::scopes::SearchListenerBase> const&)@Base" 0.4.0+14.04.20140312.1
77 (c++)"unity::scopes::internal::ScopeImpl::search(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unity::scopes::SearchMetadata const&, std::shared_ptr<unity::scopes::SearchListenerBase> const&)@Base" 0.4.0+14.04.20140312.1
78- (c++)"unity::scopes::internal::ScopeImpl::search(unity::scopes::CannedQuery const&, unity::scopes::SearchMetadata const&, std::shared_ptr<unity::scopes::SearchListenerBase> const&)@Base" 0.4.0+14.04.20140312.1
79+ (c++)"unity::scopes::internal::ScopeImpl::search(unity::scopes::CannedQuery const&, unity::scopes::SearchMetadata const&, std::vector<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > const&, std::shared_ptr<unity::scopes::SearchListenerBase> const&)@Base" 0replaceme
80 (c++)"unity::scopes::internal::ScopeLoader::libpath() const@Base" 0.4.0+14.04.20140312.1
81 (c++)"unity::scopes::internal::ScopeLoader::load(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.6.0+14.10.20140804.1
82 (c++)"unity::scopes::internal::ScopeLoader::scope_base() const@Base" 0.4.0+14.04.20140312.1
83@@ -533,7 +535,7 @@
84 (c++)"unity::scopes::internal::ScopeObject::query(std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::MiddlewareBase*, std::function<std::shared_ptr<unity::scopes::QueryBase> ()> const&, std::function<std::shared_ptr<unity::scopes::internal::QueryObjectBase> (std::shared_ptr<unity::scopes::QueryBase>, std::shared_ptr<unity::scopes::internal::MWQueryCtrl>)> const&)@Base" 0.4.0+14.04.20140312.1
85 (c++)"unity::scopes::internal::ScopeObject::~ScopeObject()@Base" 0.4.0+14.04.20140312.1
86 (c++)"unity::scopes::internal::ScopeObject::ScopeObject(unity::scopes::internal::RuntimeImpl*, unity::scopes::ScopeBase*, bool)@Base" 0.6.2+rtm+rtm+rtm+14.09.20140818
87- (c++)"unity::scopes::internal::ScopeObject::search(unity::scopes::CannedQuery const&, unity::scopes::SearchMetadata const&, std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::InvokeInfo const&)@Base" 0.4.0+14.04.20140312.1
88+ (c++)"unity::scopes::internal::ScopeObject::search(unity::scopes::CannedQuery const&, unity::scopes::SearchMetadata const&, std::map<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, unity::scopes::Variant, std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, unity::scopes::Variant> > > const&, std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::InvokeInfo const&)@Base" 0replaceme
89 (c++)"unity::scopes::internal::smartscopes::SSConfig::http_reply_timeout() const@Base" 0.4.4+14.10.20140508
90 (c++)"unity::scopes::internal::smartscopes::SSConfig::reg_refresh_fail_timeout() const@Base" 0.4.4+14.10.20140508
91 (c++)"unity::scopes::internal::smartscopes::SSConfig::reg_refresh_rate() const@Base" 0.4.4+14.10.20140508
92@@ -559,7 +561,7 @@
93 (c++)"unity::scopes::internal::smartscopes::SSScopeObject::perform_action(unity::scopes::Result const&, unity::scopes::ActionMetadata const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::InvokeInfo const&)@Base" 0.4.0+14.04.20140312.1
94 (c++)"unity::scopes::internal::smartscopes::SSScopeObject::preview(unity::scopes::Result const&, unity::scopes::ActionMetadata const&, std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::InvokeInfo const&)@Base" 0.4.0+14.04.20140312.1
95 (c++)"unity::scopes::internal::smartscopes::SSScopeObject::query(unity::scopes::internal::InvokeInfo const&, std::shared_ptr<unity::scopes::internal::MWReply> const&, std::function<std::shared_ptr<unity::scopes::QueryBase> ()> const&, std::function<void (std::shared_ptr<unity::scopes::QueryBase>)> const&)@Base" 0.4.0+14.04.20140312.1
96- (c++)"unity::scopes::internal::smartscopes::SSScopeObject::search(unity::scopes::CannedQuery const&, unity::scopes::SearchMetadata const&, std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::InvokeInfo const&)@Base" 0.4.0+14.04.20140312.1
97+ (c++)"unity::scopes::internal::smartscopes::SSScopeObject::search(unity::scopes::CannedQuery const&, unity::scopes::SearchMetadata const&, std::map<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, unity::scopes::Variant, std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, unity::scopes::Variant> > > const&, std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::InvokeInfo const&)@Base" 0replaceme
98 (c++)"unity::scopes::internal::smartscopes::SSScopeObject::~SSScopeObject()@Base" 0.4.0+14.04.20140312.1
99 (c++)"unity::scopes::internal::smartscopes::SSScopeObject::SSScopeObject(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::shared_ptr<unity::scopes::internal::MiddlewareBase>, std::shared_ptr<unity::scopes::internal::smartscopes::SSRegistryObject>)@Base" 0.4.0+14.04.20140312.1
100 (c++)"unity::scopes::internal::StateReceiverObject::push_state(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unity::scopes::internal::StateReceiverObject::State const&)@Base" 0.4.2+14.04.20140404.2
101
102=== modified file 'include/unity/scopes/Category.h'
103--- include/unity/scopes/Category.h 2014-11-03 05:31:30 +0000
104+++ include/unity/scopes/Category.h 2015-01-16 00:25:35 +0000
105@@ -42,7 +42,7 @@
106 \brief A set of related results returned by a scope
107 and displayed within a single pane in the Unity dash.
108
109- To create a Category, use ReplyProxy::register_category.
110+ To create a Category, use SearchReplyProxy::register_category.
111 \see ResultItem
112 */
113 class Category
114
115=== modified file 'include/unity/scopes/QueryBase.h'
116--- include/unity/scopes/QueryBase.h 2014-11-03 05:31:30 +0000
117+++ include/unity/scopes/QueryBase.h 2015-01-16 00:25:35 +0000
118@@ -123,8 +123,8 @@
119 private:
120 friend class internal::QueryObject; // So QueryObject can call cancel()
121 friend class internal::smartscopes::SSQueryObject; // So SSQueryObject can call cancel()
122- friend class internal::ScopeObject; // So ScopeObject can call set_metadata() and set_department_id()
123- friend class internal::smartscopes::SSScopeObject; // So SSQueryObject can call set_department_id()
124+ friend class internal::ScopeObject; // So ScopeObject can call set_department_id()
125+ friend class internal::smartscopes::SSScopeObject; // So SSScopeObject can call set_department_id()
126 };
127
128 } // namespace scopes
129
130=== modified file 'include/unity/scopes/QueryMetadata.h'
131--- include/unity/scopes/QueryMetadata.h 2014-11-03 05:31:30 +0000
132+++ include/unity/scopes/QueryMetadata.h 2015-01-16 00:25:35 +0000
133@@ -31,6 +31,7 @@
134 {
135
136 class QueryMetadataImpl;
137+class SearchQueryBaseImpl;
138
139 }
140
141@@ -96,6 +97,8 @@
142
143 std::unique_ptr<internal::QueryMetadataImpl> p;
144 QueryMetadata(internal::QueryMetadataImpl* impl);
145+
146+ friend class internal::SearchQueryBaseImpl; // Allow access for loop detection
147 /// @endcond
148 };
149
150
151=== modified file 'include/unity/scopes/SearchMetadata.h'
152--- include/unity/scopes/SearchMetadata.h 2014-11-03 05:31:30 +0000
153+++ include/unity/scopes/SearchMetadata.h 2015-01-16 00:25:35 +0000
154@@ -31,8 +31,9 @@
155
156 namespace internal
157 {
158+
159 class SearchMetadataImpl;
160-class ScopeObject;
161+
162 }
163
164 /**
165
166=== modified file 'include/unity/scopes/SearchQueryBase.h'
167--- include/unity/scopes/SearchQueryBase.h 2014-11-03 05:31:30 +0000
168+++ include/unity/scopes/SearchQueryBase.h 2015-01-16 00:25:35 +0000
169@@ -149,10 +149,10 @@
170 void set_department_id(std::string const& department_id);
171 std::string department_id() const;
172
173- friend class internal::QueryObject; // So QueryObject can call cancel() and set_department_id()
174- friend class internal::ScopeObject; // So ScopeObject can call set_metadata() and set_department_id()
175- friend class internal::smartscopes::SSQueryObject; // So SSQueryObject can call cancel()
176- friend class internal::smartscopes::SSScopeObject; // So SSQueryObject can call set_department_id()
177+ friend class internal::QueryObject; // So QueryObject can call cancel() and set_department_id()
178+ friend class internal::ScopeObject; // So ScopeObject can set department and query context
179+ friend class internal::smartscopes::SSQueryObject; // So SSQueryObject can call cancel()
180+ friend class internal::smartscopes::SSScopeObject; // So SSScopeObject can call set_department_id()
181 };
182
183 } // namespace scopes
184
185=== modified file 'include/unity/scopes/internal/MWScope.h'
186--- include/unity/scopes/internal/MWScope.h 2014-11-03 05:31:30 +0000
187+++ include/unity/scopes/internal/MWScope.h 2015-01-16 00:25:35 +0000
188@@ -39,10 +39,25 @@
189 public:
190 virtual ~MWScope();
191
192- virtual QueryCtrlProxy search(CannedQuery const& query, VariantMap const& hints, MWReplyProxy const& reply) = 0;
193- virtual QueryCtrlProxy activate(VariantMap const& result, VariantMap const& hints, MWReplyProxy const& reply) = 0;
194- virtual QueryCtrlProxy perform_action(VariantMap const& result, VariantMap const& hints, std::string const& widget_id, std::string const& action_id, MWReplyProxy const& reply) = 0;
195- virtual QueryCtrlProxy preview(VariantMap const& result, VariantMap const& hints, MWReplyProxy const& reply) = 0;
196+ virtual QueryCtrlProxy search(CannedQuery const& query,
197+ VariantMap const& hints,
198+ VariantMap const& details,
199+ MWReplyProxy const& reply) = 0;
200+
201+ virtual QueryCtrlProxy activate(VariantMap const& result,
202+ VariantMap const& hints,
203+ MWReplyProxy const& reply) = 0;
204+
205+ virtual QueryCtrlProxy perform_action(VariantMap const& result,
206+ VariantMap const& hints,
207+ std::string const& widget_id,
208+ std::string const& action_id,
209+ MWReplyProxy const& reply) = 0;
210+
211+ virtual QueryCtrlProxy preview(VariantMap const& result,
212+ VariantMap const& hints,
213+ MWReplyProxy const& reply) = 0;
214+
215 virtual bool debug_mode() = 0;
216
217 protected:
218
219=== modified file 'include/unity/scopes/internal/ScopeImpl.h'
220--- include/unity/scopes/internal/ScopeImpl.h 2014-11-03 05:31:30 +0000
221+++ include/unity/scopes/internal/ScopeImpl.h 2015-01-16 00:25:35 +0000
222@@ -18,13 +18,14 @@
223
224 #pragma once
225
226+#include <unity/scopes/ActivationListenerBase.h>
227 #include <unity/scopes/internal/MWScopeProxyFwd.h>
228 #include <unity/scopes/internal/ObjectImpl.h>
229+#include <unity/scopes/internal/SearchQueryBaseImpl.h>
230+#include <unity/scopes/PreviewListenerBase.h>
231 #include <unity/scopes/QueryCtrlProxyFwd.h>
232+#include <unity/scopes/Scope.h>
233 #include <unity/scopes/SearchListenerBase.h>
234-#include <unity/scopes/ActivationListenerBase.h>
235-#include <unity/scopes/PreviewListenerBase.h>
236-#include <unity/scopes/Scope.h>
237
238 namespace unity
239 {
240@@ -48,6 +49,8 @@
241 ScopeImpl(MWScopeProxy const& mw_proxy, RuntimeImpl* runtime, std::string const& scope_id);
242 virtual ~ScopeImpl();
243
244+ RuntimeImpl* runtime() const;
245+
246 virtual QueryCtrlProxy search(std::string const& q,
247 SearchMetadata const& metadata,
248 SearchListenerBase::SPtr const& reply) override;
249@@ -63,9 +66,17 @@
250 SearchMetadata const& metadata,
251 SearchListenerBase::SPtr const& reply) override;
252
253+ QueryCtrlProxy search(std::string const& query_string,
254+ std::string const& department_id,
255+ FilterState const& filter_state,
256+ SearchMetadata const& metadata,
257+ SearchQueryBaseImpl::History const& history,
258+ SearchListenerBase::SPtr const& reply); // Not remote, hence not override
259+
260 QueryCtrlProxy search(CannedQuery const& query,
261 SearchMetadata const& metadata,
262- SearchListenerBase::SPtr const& reply); // Not remote, hence not override
263+ SearchQueryBaseImpl::History const& history,
264+ SearchListenerBase::SPtr const& reply); // Not remote, hence not override
265
266 virtual QueryCtrlProxy activate(Result const& result,
267 ActionMetadata const& metadata,
268
269=== modified file 'include/unity/scopes/internal/ScopeObject.h'
270--- include/unity/scopes/internal/ScopeObject.h 2014-11-03 05:31:30 +0000
271+++ include/unity/scopes/internal/ScopeObject.h 2015-01-16 00:25:35 +0000
272@@ -56,14 +56,15 @@
273
274 // Remote operation implementations
275 virtual MWQueryCtrlProxy search(CannedQuery const& q,
276- SearchMetadata const& hints,
277- MWReplyProxy const& reply,
278- InvokeInfo const& info) override;
279+ SearchMetadata const& hints,
280+ VariantMap const& context,
281+ MWReplyProxy const& reply,
282+ InvokeInfo const& info) override;
283
284 virtual MWQueryCtrlProxy activate(Result const& result,
285- ActionMetadata const& hints,
286- MWReplyProxy const &reply,
287- InvokeInfo const& info) override;
288+ ActionMetadata const& hints,
289+ MWReplyProxy const &reply,
290+ InvokeInfo const& info) override;
291
292 virtual MWQueryCtrlProxy perform_action(Result const& result,
293 ActionMetadata const& hints,
294
295=== modified file 'include/unity/scopes/internal/ScopeObjectBase.h'
296--- include/unity/scopes/internal/ScopeObjectBase.h 2014-11-20 05:32:53 +0000
297+++ include/unity/scopes/internal/ScopeObjectBase.h 2015-01-16 00:25:35 +0000
298@@ -46,6 +46,7 @@
299
300 virtual MWQueryCtrlProxy search(CannedQuery const& query,
301 SearchMetadata const& hints,
302+ VariantMap const& context,
303 MWReplyProxy const& reply,
304 InvokeInfo const& info) = 0;
305
306
307=== modified file 'include/unity/scopes/internal/SearchQueryBaseImpl.h'
308--- include/unity/scopes/internal/SearchQueryBaseImpl.h 2014-11-03 05:31:30 +0000
309+++ include/unity/scopes/internal/SearchQueryBaseImpl.h 2015-01-16 00:25:35 +0000
310@@ -41,21 +41,17 @@
311 SearchQueryBaseImpl(CannedQuery const& query, SearchMetadata const& metadata);
312 CannedQuery query() const;
313 SearchMetadata search_metadata() const;
314+
315 void set_department_id(std::string const& department_id);
316 std::string department_id() const;
317
318- QueryCtrlProxy subsearch(ScopeProxy const& scope,
319- std::string const& query_string,
320- SearchListenerBase::SPtr const& reply);
321- QueryCtrlProxy subsearch(ScopeProxy const& scope,
322- std::string const& query_string,
323- FilterState const& filter_state,
324- SearchListenerBase::SPtr const& reply);
325- QueryCtrlProxy subsearch(ScopeProxy const& scope,
326- std::string const& query_string,
327- std::string const& department_id,
328- FilterState const& filter_state,
329- SearchListenerBase::SPtr const& reply);
330+ void set_client_id(const std::string& id);
331+
332+ typedef std::tuple<std::string, std::string, std::string> HistoryData;
333+ typedef std::vector<HistoryData> History;
334+
335+ void set_history(History const& h);
336+
337 QueryCtrlProxy subsearch(ScopeProxy const& scope,
338 std::string const& query_string,
339 std::string const& department_id,
340@@ -67,12 +63,17 @@
341 bool valid() const override;
342
343 private:
344+ CannedQuery const canned_query_;
345+ SearchMetadata const search_metadata_;
346+
347+ mutable std::mutex mutex_;
348 bool valid_;
349- mutable std::mutex mutex_;
350- const CannedQuery canned_query_;
351- const SearchMetadata search_metadata_;
352 std::string department_id_;
353+ std::string client_id_;
354+ History history_;
355 std::vector<QueryCtrlProxy> subqueries_;
356+
357+ QueryCtrlProxy check_for_query_loop(ScopeProxy const& scope, SearchListenerBase::SPtr const& reply);
358 };
359
360 } // namespace internal
361
362=== modified file 'include/unity/scopes/internal/smartscopes/SSScopeObject.h'
363--- include/unity/scopes/internal/smartscopes/SSScopeObject.h 2014-11-20 05:32:53 +0000
364+++ include/unity/scopes/internal/smartscopes/SSScopeObject.h 2015-01-16 00:25:35 +0000
365@@ -52,9 +52,10 @@
366
367 // Remote operation implementations
368 MWQueryCtrlProxy search(CannedQuery const& q,
369- SearchMetadata const& hints,
370- MWReplyProxy const& reply,
371- InvokeInfo const& info) override;
372+ SearchMetadata const& hints,
373+ VariantMap const& context,
374+ MWReplyProxy const& reply,
375+ InvokeInfo const& info) override;
376
377 MWQueryCtrlProxy activate(Result const& result,
378 ActionMetadata const& hints,
379
380=== modified file 'include/unity/scopes/internal/zmq_middleware/ZmqScope.h'
381--- include/unity/scopes/internal/zmq_middleware/ZmqScope.h 2014-11-03 05:31:30 +0000
382+++ include/unity/scopes/internal/zmq_middleware/ZmqScope.h 2015-01-16 00:25:35 +0000
383@@ -45,8 +45,9 @@
384 virtual ~ZmqScope();
385
386 virtual QueryCtrlProxy search(CannedQuery const& query,
387- VariantMap const& hints,
388- MWReplyProxy const& reply) override;
389+ VariantMap const& hints,
390+ VariantMap const& context,
391+ MWReplyProxy const& reply) override;
392
393 virtual QueryCtrlProxy activate(VariantMap const& result,
394 VariantMap const& hints,
395
396=== modified file 'include/unity/scopes/utility/internal/BufferedResultForwarderImpl.h'
397--- include/unity/scopes/utility/internal/BufferedResultForwarderImpl.h 2014-12-01 09:47:04 +0000
398+++ include/unity/scopes/utility/internal/BufferedResultForwarderImpl.h 2015-01-16 00:25:35 +0000
399@@ -55,8 +55,8 @@
400 std::atomic<bool> ready_;
401 std::atomic<bool> has_previous_;
402 std::atomic<bool> previous_ready_;
403- unity::scopes::SearchReplyProxy const upstream_; // Immutable
404- BufferedResultForwarder::SPtr next_; // Immutable
405+ unity::scopes::SearchReplyProxy const upstream_;
406+ BufferedResultForwarder::SPtr const next_;
407 };
408
409 } // namespace internal
410
411=== modified file 'include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h'
412--- include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h 2014-12-01 01:51:38 +0000
413+++ include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h 2015-01-16 00:25:35 +0000
414@@ -78,7 +78,7 @@
415
416 private:
417 std::mutex mutex_;
418- unity::scopes::SearchReplyProxy upstream_; // Immutable
419+ unity::scopes::SearchReplyProxy const upstream_;
420 std::atomic<bool> buffer_;
421 std::vector<CategorisedResult> results_;
422 };
423
424=== modified file 'src/scopes/SearchQueryBase.cpp'
425--- src/scopes/SearchQueryBase.cpp 2014-06-12 16:16:01 +0000
426+++ src/scopes/SearchQueryBase.cpp 2015-01-16 00:25:35 +0000
427@@ -61,7 +61,7 @@
428 string const& query_string,
429 SearchListenerBase::SPtr const& reply)
430 {
431- return fwd()->subsearch(scope, query_string, reply);
432+ return subsearch(scope, query_string, "", FilterState(), fwd()->search_metadata(), reply);
433 }
434
435 QueryCtrlProxy SearchQueryBase::subsearch(ScopeProxy const& scope,
436@@ -69,7 +69,7 @@
437 FilterState const& filter_state,
438 SearchListenerBase::SPtr const& reply)
439 {
440- return fwd()->subsearch(scope, query_string, filter_state, reply);
441+ return subsearch(scope, query_string, "", filter_state, fwd()->search_metadata(), reply);
442 }
443
444 QueryCtrlProxy SearchQueryBase::subsearch(ScopeProxy const& scope,
445@@ -78,7 +78,7 @@
446 FilterState const& filter_state,
447 SearchListenerBase::SPtr const& reply)
448 {
449- return fwd()->subsearch(scope, query_string, department_id, filter_state, reply);
450+ return subsearch(scope, query_string, department_id, filter_state, fwd()->search_metadata(), reply);
451 }
452
453 QueryCtrlProxy SearchQueryBase::subsearch(ScopeProxy const& scope,
454
455=== modified file 'src/scopes/internal/JsonCppNode.cpp'
456--- src/scopes/internal/JsonCppNode.cpp 2014-08-18 08:12:22 +0000
457+++ src/scopes/internal/JsonCppNode.cpp 2015-01-16 00:25:35 +0000
458@@ -252,6 +252,11 @@
459
460 bool JsonCppNode::has_node(std::string const& node_name) const
461 {
462+ if (!root_)
463+ {
464+ throw unity::LogicException("Current node is empty");
465+ }
466+
467 return root_.isMember(node_name);
468 }
469
470
471=== modified file 'src/scopes/internal/JsonSettingsSchema.cpp'
472--- src/scopes/internal/JsonSettingsSchema.cpp 2014-11-20 21:33:33 +0000
473+++ src/scopes/internal/JsonSettingsSchema.cpp 2015-01-16 00:25:35 +0000
474@@ -181,7 +181,11 @@
475 void Setting::set_default_value(Json::Value const& v, Type expected_type)
476 {
477 auto v_param = v[parameters_key];
478- if (!v_param.isObject())
479+ if (v_param.isNull())
480+ {
481+ return;
482+ }
483+ else if (!v_param.isObject())
484 {
485 throw ResourceException("JsonSettingsSchema(): expected value of type object for \"parameters\", id = \"" + id_ + "\"");
486 }
487
488=== modified file 'src/scopes/internal/QueryCtrlImpl.cpp'
489--- src/scopes/internal/QueryCtrlImpl.cpp 2014-11-20 05:47:58 +0000
490+++ src/scopes/internal/QueryCtrlImpl.cpp 2015-01-16 00:25:35 +0000
491@@ -46,9 +46,6 @@
492 {
493 // We remember the reply proxy so, when the query is cancelled, we can
494 // inform the reply object belonging to this query that the query is finished.
495- assert(reply_proxy);
496-
497- lock_guard<mutex> lock(mutex_);
498 ready_ = ctrl_proxy != nullptr;
499 cancelled_ = false;
500 }
501@@ -89,12 +86,16 @@
502 void QueryCtrlImpl::set_proxy(MWQueryCtrlProxy const& p)
503 {
504 assert(proxy() == nullptr);
505- ObjectImpl::set_proxy(p);
506-
507- bool need_cancel;
508-
509+
510+ bool need_cancel = false;
511 {
512 lock_guard<mutex> lock(mutex_);
513+
514+ if (!reply_proxy_)
515+ {
516+ return;
517+ }
518+ ObjectImpl::set_proxy(p);
519 ready_ = true;
520 need_cancel = cancelled_;
521 } // Unlock
522
523=== modified file 'src/scopes/internal/ScopeImpl.cpp'
524--- src/scopes/internal/ScopeImpl.cpp 2014-11-20 05:29:23 +0000
525+++ src/scopes/internal/ScopeImpl.cpp 2015-01-16 00:25:35 +0000
526@@ -55,6 +55,11 @@
527 {
528 }
529
530+RuntimeImpl* ScopeImpl::runtime() const
531+{
532+ return runtime_;
533+}
534+
535 QueryCtrlProxy ScopeImpl::search(std::string const& query_string,
536 std::string const& department_id,
537 FilterState const& filter_state,
538@@ -63,7 +68,7 @@
539 {
540 CannedQuery query(scope_id_, query_string, department_id);
541 query.set_filter_state(filter_state);
542- return search(query, metadata, reply);
543+ return search(query, metadata, SearchQueryBaseImpl::History(), reply);
544 }
545
546 QueryCtrlProxy ScopeImpl::search(std::string const& query_string,
547@@ -71,23 +76,34 @@
548 SearchMetadata const& metadata,
549 SearchListenerBase::SPtr const& reply)
550 {
551- CannedQuery query(scope_id_);
552- query.set_query_string(query_string);
553+ CannedQuery query(scope_id_, query_string, "");
554 query.set_filter_state(filter_state);
555- return search(query, metadata, reply);
556+ return search(query, metadata, SearchQueryBaseImpl::History(), reply);
557 }
558
559 QueryCtrlProxy ScopeImpl::search(string const& query_string,
560 SearchMetadata const& metadata,
561 SearchListenerBase::SPtr const& reply)
562 {
563- CannedQuery query(scope_id_);
564- query.set_query_string(query_string);
565- return search(query, metadata, reply);
566+ CannedQuery query(scope_id_, query_string, "");
567+ return search(query, metadata, SearchQueryBaseImpl::History(), reply);
568+}
569+
570+QueryCtrlProxy ScopeImpl::search(std::string const& query_string,
571+ std::string const& department_id,
572+ FilterState const& filter_state,
573+ SearchMetadata const& metadata,
574+ SearchQueryBaseImpl::History const& history,
575+ SearchListenerBase::SPtr const& reply)
576+{
577+ CannedQuery query(scope_id_, query_string, department_id);
578+ query.set_filter_state(filter_state);
579+ return search(query, metadata, history, reply);
580 }
581
582 QueryCtrlProxy ScopeImpl::search(CannedQuery const& query,
583 SearchMetadata const& metadata,
584+ SearchQueryBaseImpl::History const& history,
585 SearchListenerBase::SPtr const& reply)
586 {
587 if (reply == nullptr)
588@@ -107,12 +123,32 @@
589 // sent in the new thread, the lambda will call into this by-now-destroyed instance.
590 auto impl = dynamic_pointer_cast<ScopeImpl>(shared_from_this());
591
592- auto send_search = [impl, query, metadata, rp, ro, ctrl]() -> void
593+ string const my_id = runtime_->scope_id();
594+ auto send_search = [my_id, impl, query, metadata, history, rp, ro, ctrl]() -> void
595 {
596 try
597 {
598+ // Create query context with our own ID and the query history for loop detection.
599+ VariantMap context;
600+ context["client_id"] = my_id;
601+
602+ VariantArray hist;
603+ for (auto const& tuple : history)
604+ {
605+ VariantMap d;
606+ d["c"] = get<0>(tuple); // Client
607+ d["a"] = get<1>(tuple); // Aggregator
608+ d["r"] = get<2>(tuple); // Receiver
609+ hist.push_back(Variant(d));
610+ }
611+ context["history"] = Variant(hist);
612+
613 // Forward the (synchronous) search() method across the bus.
614- auto real_ctrl = dynamic_pointer_cast<QueryCtrlImpl>(impl->fwd()->search(query, metadata.serialize(), rp));
615+ auto real_ctrl = dynamic_pointer_cast<QueryCtrlImpl>(impl->fwd()->search(query,
616+ metadata.serialize(),
617+ context,
618+ rp));
619+
620 assert(real_ctrl);
621
622 // Call has completed now, so we update the MWQueryCtrlProxy for the fake proxy
623
624=== modified file 'src/scopes/internal/ScopeObject.cpp'
625--- src/scopes/internal/ScopeObject.cpp 2014-11-18 07:04:04 +0000
626+++ src/scopes/internal/ScopeObject.cpp 2015-01-16 00:25:35 +0000
627@@ -27,6 +27,8 @@
628 #include <unity/scopes/internal/QueryObject.h>
629 #include <unity/scopes/internal/RuntimeImpl.h>
630 #include <unity/scopes/internal/ScopeBaseImpl.h>
631+#include <unity/scopes/internal/SearchMetadataImpl.h>
632+#include <unity/scopes/internal/SearchQueryBaseImpl.h>
633 #include <unity/scopes/ScopeBase.h>
634 #include <unity/UnityExceptions.h>
635
636@@ -149,13 +151,44 @@
637
638 MWQueryCtrlProxy ScopeObject::search(CannedQuery const& q,
639 SearchMetadata const& hints,
640+ VariantMap const& context,
641 MWReplyProxy const& reply,
642 InvokeInfo const& info)
643 {
644 return query(reply, info.mw,
645- [&q, &hints, this]() -> SearchQueryBase::UPtr {
646+ [&q, &hints, &context, this]() -> SearchQueryBase::UPtr {
647 auto search_query = this->scope_base_->search(q, hints);
648 search_query->set_department_id(q.department_id());
649+
650+ auto sqb = dynamic_cast<SearchQueryBaseImpl*>(search_query->fwd());
651+ assert(sqb);
652+
653+ // Set client ID and history that we received in the SearchQueryBase
654+ // for loop detection.
655+ auto const c_it = context.find("client_id");
656+ if (c_it != context.end())
657+ {
658+ string client_id;
659+ client_id = c_it->second.get_string();
660+ sqb->set_client_id(client_id);
661+ }
662+
663+ auto const h_it = context.find("history");
664+ if (h_it != context.end())
665+ {
666+ auto const hlist = h_it->second.get_array();
667+ SearchQueryBaseImpl::History history;
668+ for (auto const& t : hlist)
669+ {
670+ string client_id = t.get_dict()["c"].get_string();
671+ string agg = t.get_dict()["a"].get_string();
672+ string recv = t.get_dict()["r"].get_string();
673+ SearchQueryBaseImpl::HistoryData hd = make_tuple(client_id, agg, recv);
674+ history.push_back(hd);
675+ }
676+ sqb->set_history(history);
677+ }
678+
679 return search_query;
680 },
681 [&reply, &hints, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
682
683=== modified file 'src/scopes/internal/SearchMetadataImpl.cpp'
684--- src/scopes/internal/SearchMetadataImpl.cpp 2014-09-02 15:47:35 +0000
685+++ src/scopes/internal/SearchMetadataImpl.cpp 2015-01-16 00:25:35 +0000
686@@ -19,8 +19,8 @@
687 #include <unity/scopes/internal/SearchMetadataImpl.h>
688 #include <unity/scopes/internal/Utils.h>
689 #include <unity/scopes/ScopeExceptions.h>
690+
691 #include <unity/UnityExceptions.h>
692-#include <sstream>
693
694 namespace unity
695 {
696@@ -50,6 +50,7 @@
697 auto it = find_or_throw("SearchMetadataImpl()", var, "cardinality");
698 cardinality_ = it->second.get_int();
699 check_cardinality("SearchMetadataImpl(VariantMap)", cardinality_);
700+
701 try
702 {
703 it = find_or_throw("SearchMetadataImpl()", var, "location");
704@@ -90,7 +91,6 @@
705 return location_;
706 }
707
708-
709 std::string SearchMetadataImpl::metadata_type() const
710 {
711 static const std::string t("search_metadata");
712
713=== modified file 'src/scopes/internal/SearchQueryBaseImpl.cpp'
714--- src/scopes/internal/SearchQueryBaseImpl.cpp 2014-08-05 06:42:04 +0000
715+++ src/scopes/internal/SearchQueryBaseImpl.cpp 2015-01-16 00:25:35 +0000
716@@ -18,9 +18,17 @@
717 */
718
719 #include <unity/scopes/internal/SearchQueryBaseImpl.h>
720+
721+#include <unity/scopes/internal/QueryCtrlImpl.h>
722+#include <unity/scopes/internal/RuntimeImpl.h>
723+#include <unity/scopes/internal/ScopeImpl.h>
724+#include <unity/scopes/internal/SearchMetadataImpl.h>
725+#include <unity/scopes/ScopeExceptions.h>
726+
727 #include <unity/UnityExceptions.h>
728-#include <unity/scopes/QueryCtrl.h>
729-#include <unity/scopes/Scope.h>
730+
731+#include <algorithm>
732+#include <cassert>
733
734 namespace unity
735 {
736@@ -35,9 +43,9 @@
737
738 SearchQueryBaseImpl::SearchQueryBaseImpl(CannedQuery const& query, SearchMetadata const& metadata)
739 : QueryBaseImpl(),
740- valid_(true),
741 canned_query_(query),
742- search_metadata_(metadata)
743+ search_metadata_(metadata),
744+ valid_(true)
745 {
746 }
747
748@@ -52,72 +60,6 @@
749 }
750
751 QueryCtrlProxy SearchQueryBaseImpl::subsearch(ScopeProxy const& scope,
752- string const& query_string,
753- SearchListenerBase::SPtr const& reply)
754-{
755- if (!scope)
756- {
757- throw InvalidArgumentException("QueryBase::subsearch(): scope cannot be nullptr");
758- }
759- if (!reply)
760- {
761- throw InvalidArgumentException("QueryBase::subsearch(): reply cannot be nullptr");
762- }
763-
764- // Forward the create request to the child scope and remember the control.
765- // This allows cancel() to forward incoming cancellations to subqueries
766- // without intervention from the scope application code.
767- QueryCtrlProxy qcp = scope->search(query_string, search_metadata_, reply);
768-
769- lock_guard<mutex> lock(mutex_);
770- subqueries_.push_back(qcp);
771- return qcp;
772-}
773-
774-QueryCtrlProxy SearchQueryBaseImpl::subsearch(ScopeProxy const& scope,
775- std::string const& query_string,
776- FilterState const& filter_state,
777- SearchListenerBase::SPtr const& reply)
778-{
779- if (!scope)
780- {
781- throw InvalidArgumentException("QueryBase::subsearch(): scope cannot be nullptr");
782- }
783- if (!reply)
784- {
785- throw InvalidArgumentException("QueryBase::subsearch(): reply cannot be nullptr");
786- }
787-
788- QueryCtrlProxy qcp = scope->search(query_string, filter_state, search_metadata_, reply);
789-
790- lock_guard<mutex> lock(mutex_);
791- subqueries_.push_back(qcp);
792- return qcp;
793-}
794-
795-QueryCtrlProxy SearchQueryBaseImpl::subsearch(ScopeProxy const& scope,
796- std::string const& query_string,
797- std::string const& department_id,
798- FilterState const& filter_state,
799- SearchListenerBase::SPtr const& reply)
800-{
801- if (!scope)
802- {
803- throw InvalidArgumentException("QueryBase::subsearch(): scope cannot be nullptr");
804- }
805- if (!reply)
806- {
807- throw InvalidArgumentException("QueryBase::subsearch(): reply cannot be nullptr");
808- }
809-
810- QueryCtrlProxy qcp = scope->search(query_string, department_id, filter_state, search_metadata_, reply);
811-
812- lock_guard<mutex> lock(mutex_);
813- subqueries_.push_back(qcp);
814- return qcp;
815-}
816-
817-QueryCtrlProxy SearchQueryBaseImpl::subsearch(ScopeProxy const& scope,
818 std::string const& query_string,
819 std::string const& department_id,
820 FilterState const& filter_state,
821@@ -133,11 +75,28 @@
822 throw InvalidArgumentException("QueryBase::subsearch(): reply cannot be nullptr");
823 }
824
825- QueryCtrlProxy qcp = scope->search(query_string, department_id, filter_state, metadata, reply);
826+ auto query_ctrl = check_for_query_loop(scope, reply);
827+ if (query_ctrl)
828+ {
829+ return query_ctrl; // Loop was detected, return dummy QueryCtrlProxy.
830+ }
831+
832+ // scope_impl can be nullptr if we use a mock scope: TypedScopeFixture<testing::Scope>
833+ // If so, we call the normal search without passing the history through because
834+ // we don't need loop detection for mock scopes.
835+ auto scope_impl = dynamic_pointer_cast<ScopeImpl>(scope);
836+ if (scope_impl)
837+ {
838+ query_ctrl = scope_impl->search(query_string, department_id, filter_state, metadata, history_, reply);
839+ }
840+ else
841+ {
842+ query_ctrl = scope->search(query_string, department_id, filter_state, metadata, reply);
843+ }
844
845 lock_guard<mutex> lock(mutex_);
846- subqueries_.push_back(qcp);
847- return qcp;
848+ subqueries_.push_back(query_ctrl); // Remember subsearch in case we get a cancel() later that we need to forward.
849+ return query_ctrl;
850 }
851
852 void SearchQueryBaseImpl::cancel()
853@@ -171,12 +130,70 @@
854 return department_id_;
855 }
856
857+void SearchQueryBaseImpl::set_client_id(std::string const& id)
858+{
859+ lock_guard<mutex> lock(mutex_);
860+ client_id_ = id;
861+}
862+
863+void SearchQueryBaseImpl::set_history(History const& h)
864+{
865+ lock_guard<mutex> lock(mutex_);
866+ history_ = h;
867+}
868+
869 bool SearchQueryBaseImpl::valid() const
870 {
871 lock_guard<mutex> lock(mutex_);
872 return valid_;
873 }
874
875+// Check if this query has been through this aggregator before by checking the
876+// history. The history is a list of <client, aggregator, receiver> scope ID tuples.
877+// If a query loops around to this aggregator, and has been sent to the same child
878+// previously on behalf of the same client, we immediately call finished to indicate
879+// an empty result set. If a loop is detected, we return a dummy QueryCtrl. Otherwise,
880+// we add the current <client, aggregator, receiver> tuple to the history before forwarding the search.
881+//
882+// Note that we do this unconditionally, whether the receiver is a leaf or an aggregator.
883+// This means that an aggregator can ask a *leaf scope* more than once for data.
884+// This works because normal searches do not check the query history (only
885+// subsearches do). But an aggregator cannot ask another *aggregator* more
886+// than once for data, even if that would not create a loop.
887+//
888+// It is possible for an aggregator to send a query to itself, but only once.
889+
890+QueryCtrlProxy SearchQueryBaseImpl::check_for_query_loop(ScopeProxy const& scope,
891+ SearchListenerBase::SPtr const& reply)
892+{
893+ shared_ptr<QueryCtrlImpl> ctrl_proxy;
894+
895+ lock_guard<mutex> lock(mutex_);
896+
897+ HistoryData tuple = make_tuple(client_id_, canned_query_.scope_id(), scope->identity());
898+ auto it = find(history_.begin(), history_.end(), tuple);
899+ if (it != history_.end())
900+ {
901+ // Query has been here before from the same client and with the same child scope as the target.
902+ reply->finished(CompletionDetails(CompletionDetails::OK,
903+ "empty result set due to aggregator loop or repeated query on aggregating scope "
904+ + get<1>(tuple)));
905+ auto scope_impl = dynamic_pointer_cast<ScopeImpl>(scope);
906+ auto logger = scope_impl->runtime()->logger();
907+ ctrl_proxy = make_shared<QueryCtrlImpl>(nullptr, nullptr, logger); // Dummy proxy in already-cancelled state
908+ BOOST_LOG_SEV(logger, Logger::Warning)
909+ << "query loop for query \"" << canned_query_.query_string()
910+ << "\", client: " << get<0>(tuple)
911+ << ", aggregator: " << get<1>(tuple)
912+ << ", receiver: " << get<2>(tuple) << endl;
913+ }
914+ else
915+ {
916+ history_.push_back(tuple);
917+ }
918+
919+ return ctrl_proxy; // null proxy if there was no loop
920+}
921
922 } // namespace internal
923
924
925=== modified file 'src/scopes/internal/smartscopes/SSScopeObject.cpp'
926--- src/scopes/internal/smartscopes/SSScopeObject.cpp 2014-11-18 07:04:04 +0000
927+++ src/scopes/internal/smartscopes/SSScopeObject.cpp 2015-01-16 00:25:35 +0000
928@@ -65,9 +65,10 @@
929 }
930
931 MWQueryCtrlProxy SSScopeObject::search(CannedQuery const& q,
932- SearchMetadata const& hints,
933- MWReplyProxy const& reply,
934- InvokeInfo const& info)
935+ SearchMetadata const& hints,
936+ VariantMap const& /* context */,
937+ MWReplyProxy const& reply,
938+ InvokeInfo const& info)
939 {
940 return query(info,
941 reply,
942
943=== modified file 'src/scopes/internal/zmq_middleware/ScopeI.cpp'
944--- src/scopes/internal/zmq_middleware/ScopeI.cpp 2014-11-18 07:04:04 +0000
945+++ src/scopes/internal/zmq_middleware/ScopeI.cpp 2015-01-16 00:25:35 +0000
946@@ -94,10 +94,12 @@
947 proxy.getEndpoint().cStr(),
948 proxy.getIdentity().cStr(),
949 proxy.getCategory().cStr()));
950+ auto context = to_variant_map(req.getContext());
951 auto delegate = dynamic_pointer_cast<ScopeObjectBase>(del());
952 assert(delegate);
953 auto ctrl_proxy = dynamic_pointer_cast<ZmqQueryCtrl>(delegate->search(query,
954 metadata,
955+ context,
956 reply_proxy,
957 to_info(current)));
958 assert(ctrl_proxy);
959
960=== modified file 'src/scopes/internal/zmq_middleware/ZmqScope.cpp'
961--- src/scopes/internal/zmq_middleware/ZmqScope.cpp 2014-11-20 05:29:23 +0000
962+++ src/scopes/internal/zmq_middleware/ZmqScope.cpp 2015-01-16 00:25:35 +0000
963@@ -77,7 +77,10 @@
964 {
965 }
966
967-QueryCtrlProxy ZmqScope::search(CannedQuery const& query, VariantMap const& hints, MWReplyProxy const& reply)
968+QueryCtrlProxy ZmqScope::search(CannedQuery const& query,
969+ VariantMap const& hints,
970+ VariantMap const& context,
971+ MWReplyProxy const& reply)
972 {
973 capnp::MallocMessageBuilder request_builder;
974 auto reply_proxy = dynamic_pointer_cast<ZmqReply>(reply);
975@@ -92,6 +95,8 @@
976 p.setEndpoint(reply_proxy->endpoint().c_str());
977 p.setIdentity(reply_proxy->identity().c_str());
978 p.setCategory(reply_proxy->target_category().c_str());
979+ auto d = in_params.initContext();
980+ to_value_dict(context, d);
981 }
982
983 auto future = mw_base()->twoway_pool()->submit([&] { return this->invoke_scope_(request_builder); });
984
985=== modified file 'src/scopes/internal/zmq_middleware/capnproto/Scope.capnp'
986--- src/scopes/internal/zmq_middleware/capnproto/Scope.capnp 2014-08-12 13:26:35 +0000
987+++ src/scopes/internal/zmq_middleware/capnproto/Scope.capnp 2015-01-16 00:25:35 +0000
988@@ -25,27 +25,17 @@
989 using Proxy = import "Proxy.capnp";
990 using ValueDict = import "ValueDict.capnp";
991
992-# Factory for queries. The Scope object runs on the ctrl-endpoint.
993+# Factory for queries.
994 # createQuery() creates a Query object on the normal endpoint, and a QueryCtrl object
995 # on the ctrl-endpoint. It then calls run() on the Query object to set it executing in its own thread, before
996 # returning the QueryCtrl proxy that permits cancellation. This guarantees that createQuery() will not block.
997
998-# Scope interface
999-#
1000-# Operations:
1001-#
1002-# Proxy createQuery(string query, ValueDict hints, Proxy reply_proxy);
1003-
1004-# The createQuery method instantiates a Query Object and its corresponding QueryCtrlObject.
1005-# The return value is the proxy to the QueryCtrl object.
1006-# The implementation of createQuery calls the Query's run() method to give a thread of control
1007-# to the application code.
1008-
1009 struct CreateQueryRequest
1010 {
1011 query @0 : ValueDict.ValueDict;
1012 hints @1 : ValueDict.ValueDict;
1013 replyProxy @2 : Proxy.Proxy;
1014+ context @3 : ValueDict.ValueDict; # Additional context for the request, such as client ID and history.
1015 }
1016
1017 struct CreateQueryResponse
1018
1019=== modified file 'test/gtest/scopes/CMakeLists.txt'
1020--- test/gtest/scopes/CMakeLists.txt 2014-12-01 01:51:38 +0000
1021+++ test/gtest/scopes/CMakeLists.txt 2015-01-16 00:25:35 +0000
1022@@ -19,6 +19,7 @@
1023 add_subdirectory(IdleShutdown)
1024 add_subdirectory(Invocation)
1025 add_subdirectory(Location)
1026+add_subdirectory(LoopDetection)
1027 add_subdirectory(OnlineAccountClient)
1028 add_subdirectory(OptionSelectorFilter)
1029 add_subdirectory(RadioButtonsFilter)
1030
1031=== added directory 'test/gtest/scopes/LoopDetection'
1032=== added file 'test/gtest/scopes/LoopDetection/CMakeLists.txt'
1033--- test/gtest/scopes/LoopDetection/CMakeLists.txt 1970-01-01 00:00:00 +0000
1034+++ test/gtest/scopes/LoopDetection/CMakeLists.txt 2015-01-16 00:25:35 +0000
1035@@ -0,0 +1,26 @@
1036+configure_file(TestRegistry.ini.in ${CMAKE_CURRENT_BINARY_DIR}/TestRegistry.ini)
1037+configure_file(Runtime.ini.in ${CMAKE_CURRENT_BINARY_DIR}/Runtime.ini)
1038+configure_file(Zmq.ini.in ${CMAKE_CURRENT_BINARY_DIR}/Zmq.ini)
1039+
1040+add_definitions(-DTEST_RUNTIME_PATH="${CMAKE_CURRENT_BINARY_DIR}")
1041+add_definitions(-DTEST_RUNTIME_FILE="${CMAKE_CURRENT_BINARY_DIR}/Runtime.ini")
1042+add_definitions(-DTEST_REGISTRY_PATH="${PROJECT_BINARY_DIR}/scoperegistry")
1043+
1044+add_executable(LoopDetection_test LoopDetection_test.cpp LoopScope.cpp)
1045+target_link_libraries(LoopDetection_test ${TESTLIBS})
1046+
1047+add_dependencies(LoopDetection_test scoperegistry scoperunner)
1048+
1049+add_test(LoopDetection LoopDetection_test)
1050+
1051+set(SCOPE_DIR "${CMAKE_CURRENT_BINARY_DIR}/scopes")
1052+
1053+foreach (scope A B C D)
1054+ file(MAKE_DIRECTORY "${SCOPE_DIR}/${scope}")
1055+ configure_file(LoopScope.ini.in ${SCOPE_DIR}/${scope}/${scope}.ini)
1056+ add_library(${scope} MODULE LoopScope.cpp)
1057+ set_target_properties(${scope}
1058+ PROPERTIES
1059+ LIBRARY_OUTPUT_DIRECTORY "${SCOPE_DIR}/${scope}/"
1060+ )
1061+endforeach()
1062
1063=== added file 'test/gtest/scopes/LoopDetection/LoopDetection_test.cpp'
1064--- test/gtest/scopes/LoopDetection/LoopDetection_test.cpp 1970-01-01 00:00:00 +0000
1065+++ test/gtest/scopes/LoopDetection/LoopDetection_test.cpp 2015-01-16 00:25:35 +0000
1066@@ -0,0 +1,249 @@
1067+/*
1068+ * Copyright (C) 2014 Canonical Ltd
1069+ *
1070+ * This program is free software: you can redistribute it and/or modify
1071+ * it under the terms of the GNU Lesser General Public License version 3 as
1072+ * published by the Free Software Foundation.
1073+ *
1074+ * This program is distributed in the hope that it will be useful,
1075+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1076+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1077+ * GNU Lesser General Public License for more details.
1078+ *
1079+ * You should have received a copy of the GNU Lesser General Public License
1080+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1081+ *
1082+ * Authored by: Michi Henning <michi.henning@canonical.com>
1083+ */
1084+
1085+#include <unity/scopes/CategorisedResult.h>
1086+#include <unity/scopes/internal/RegistryObject.h>
1087+#include <unity/scopes/internal/RuntimeImpl.h>
1088+#include <unity/scopes/internal/ScopeImpl.h>
1089+
1090+#include <gtest/gtest.h>
1091+
1092+#include "LoopScope.h"
1093+
1094+using namespace std;
1095+using namespace unity::scopes;
1096+using namespace unity::scopes::internal;
1097+
1098+class CountReceiver : public SearchListenerBase
1099+{
1100+public:
1101+ CountReceiver()
1102+ : query_complete_(false)
1103+ {
1104+ }
1105+
1106+ virtual void push(CategorisedResult result) override
1107+ {
1108+ string cat = result.category()->id();
1109+ string title = result.title();
1110+
1111+ lock_guard<mutex> lock(mutex_);
1112+ auto it = results_.find(cat);
1113+ if (it != results_.end())
1114+ {
1115+ it->second.push_back(title);
1116+ }
1117+ else
1118+ {
1119+ results_.insert(make_pair(cat, vector<string>{ title }));
1120+ }
1121+ cerr << "inserted " << cat << ", " << title << endl;
1122+ }
1123+
1124+ virtual void finished(CompletionDetails const& details) override
1125+ {
1126+ EXPECT_EQ(CompletionDetails::OK, details.status());
1127+ lock_guard<mutex> lock(mutex_);
1128+ query_complete_ = true;
1129+ cond_.notify_one();
1130+ }
1131+
1132+ void wait_until_finished()
1133+ {
1134+ unique_lock<mutex> lock(mutex_);
1135+ cond_.wait(lock, [this] { return this->query_complete_; });
1136+ query_complete_ = false;
1137+ }
1138+
1139+ map<string, vector<string>> results() const
1140+ {
1141+ lock_guard<mutex> lock(mutex_);
1142+
1143+ return results_;
1144+ }
1145+
1146+private:
1147+ map<string, vector<string>> results_;
1148+ bool query_complete_;
1149+ mutable mutex mutex_;
1150+ condition_variable cond_;
1151+};
1152+
1153+class LoopDetectionTest : public ::testing::Test
1154+{
1155+public:
1156+ LoopDetectionTest()
1157+ {
1158+ runtime_ = Runtime::create(TEST_RUNTIME_FILE);
1159+ auto reg = runtime_->registry();
1160+ auto meta = reg->get_metadata("A"); // First scope to receive a search is always A
1161+ scope_ = meta.proxy();
1162+ }
1163+
1164+ ScopeProxy scope() const
1165+ {
1166+ return scope_;
1167+ }
1168+
1169+private:
1170+ Runtime::UPtr runtime_;
1171+ ScopeProxy scope_;
1172+};
1173+
1174+// Stop warnings about unused return value from system()
1175+
1176+#pragma GCC diagnostic push
1177+#pragma GCC diagnostic ignored "-Wunused-result"
1178+
1179+// The command file for each scope is called A.cmd, B.cmd, etc. It contains scope IDs, one per
1180+// line. For each scope ID in its command file, the scope sends a subsearch to that scope.
1181+// Once a scope's subsearch completes, it pushes a single result with the scope's ID as the
1182+// category ID. This allows us to set up various callgraphs by writing to the various command files.
1183+
1184+TEST_F(LoopDetectionTest, no_error)
1185+{
1186+ system("cat >A.cmd << EOF\nB\nC\nEOF");
1187+ system(">B.cmd");
1188+ system(">C.cmd");
1189+ auto receiver = make_shared<CountReceiver>();
1190+ scope()->search("A", SearchMetadata("unused", "unused"), receiver);
1191+ receiver->wait_until_finished();
1192+
1193+ auto r = receiver->results();
1194+ EXPECT_EQ(3, r.size());
1195+ EXPECT_TRUE(r.find("A") != r.end());
1196+ EXPECT_TRUE(r.find("B") != r.end());
1197+ EXPECT_TRUE(r.find("C") != r.end());
1198+}
1199+
1200+TEST_F(LoopDetectionTest, diamond)
1201+{
1202+ system("cat >A.cmd << EOF\nB\nC\nEOF");
1203+ system("cat >B.cmd << EOF\nD\nEOF");
1204+ system("cat >C.cmd << EOF\nD\nEOF");
1205+ system(">D.cmd");
1206+ auto receiver = make_shared<CountReceiver>();
1207+ scope()->search("A", SearchMetadata("unused", "unused"), receiver);
1208+ receiver->wait_until_finished();
1209+
1210+ auto r = receiver->results();
1211+ EXPECT_EQ(4, r.size());
1212+ EXPECT_TRUE(r.find("A") != r.end());
1213+ EXPECT_TRUE(r.find("B") != r.end());
1214+ EXPECT_TRUE(r.find("C") != r.end());
1215+ EXPECT_TRUE(r.find("D") != r.end());
1216+ EXPECT_EQ(2, r.find("D")->second.size()); // B, and C each forwarded to D, so there must be 2 results.
1217+}
1218+
1219+TEST_F(LoopDetectionTest, immediate_loop)
1220+{
1221+ system("cat >A.cmd << EOF\nA\nEOF");
1222+ auto receiver = make_shared<CountReceiver>();
1223+ scope()->search("A", SearchMetadata("unused", "unused"), receiver);
1224+ receiver->wait_until_finished();
1225+
1226+ auto r = receiver->results();
1227+ EXPECT_EQ(1, r.size());
1228+ EXPECT_EQ(3, r.find("A")->second.size()); // "A" has sent the query twice to itself, second attempt stops loop
1229+}
1230+
1231+TEST_F(LoopDetectionTest, intermediate_loop)
1232+{
1233+ system("cat >A.cmd << EOF\nB\nEOF");
1234+ system("cat >B.cmd << EOF\nC\nEOF");
1235+ system("cat >C.cmd << EOF\nA\nEOF");
1236+ auto receiver = make_shared<CountReceiver>();
1237+ scope()->search("A", SearchMetadata("unused", "unused"), receiver);
1238+ receiver->wait_until_finished();
1239+
1240+ auto r = receiver->results();
1241+ EXPECT_EQ(3, r.size());
1242+ EXPECT_EQ(2, r.find("A")->second.size()); // "A" got results from B and C
1243+ EXPECT_EQ(2, r.find("B")->second.size()); // "B" got results from C and A
1244+ EXPECT_EQ(1, r.find("C")->second.size()); // "C" got result from A
1245+}
1246+
1247+TEST_F(LoopDetectionTest, repeated_search_on_leaf)
1248+{
1249+ system("cat >A.cmd << EOF\nB\nC\nD\nEOF");
1250+ system("cat >B.cmd << EOF\nD\nD\nEOF");
1251+ system("cat >C.cmd << EOF\nD\nEOF");
1252+ system(">D.cmd");
1253+ auto receiver = make_shared<CountReceiver>();
1254+ scope()->search("A", SearchMetadata("unused", "unused"), receiver);
1255+ receiver->wait_until_finished();
1256+
1257+ auto r = receiver->results();
1258+ EXPECT_EQ(4, r.size());
1259+ EXPECT_EQ(1, r.find("A")->second.size());
1260+ EXPECT_EQ(1, r.find("B")->second.size());
1261+ EXPECT_EQ(1, r.find("C")->second.size());
1262+ EXPECT_EQ(3, r.find("D")->second.size());
1263+}
1264+
1265+TEST_F(LoopDetectionTest, repeated_search_on_aggregator)
1266+{
1267+ system("cat >A.cmd << EOF\nB\nEOF");
1268+ system("cat >B.cmd << EOF\nC\nC\nEOF");
1269+ system(">C.cmd << EOF\nD\nEOF");
1270+ system(">D.cmd");
1271+ auto receiver = make_shared<CountReceiver>();
1272+ scope()->search("A", SearchMetadata("unused", "unused"), receiver);
1273+ receiver->wait_until_finished();
1274+
1275+ auto r = receiver->results();
1276+ EXPECT_EQ(3, r.size());
1277+ EXPECT_EQ(1, r.find("A")->second.size());
1278+ EXPECT_EQ(1, r.find("B")->second.size());
1279+ EXPECT_EQ(1, r.find("C")->second.size());
1280+}
1281+
1282+#pragma GCC diagnostic pop
1283+
1284+int main(int argc, char **argv)
1285+{
1286+ ::testing::InitGoogleTest(&argc, argv);
1287+
1288+ int rc = 0;
1289+
1290+ // Set the "TEST_DESKTOP_FILES_DIR" env var before forking as not to create desktop files in ~/.local
1291+ putenv(const_cast<char*>("TEST_DESKTOP_FILES_DIR=" TEST_RUNTIME_PATH));
1292+
1293+ auto rpid = fork();
1294+ if (rpid == 0)
1295+ {
1296+ const char* const args[] = {"scoperegistry [LoopDetecttion_test]", TEST_RUNTIME_FILE, nullptr};
1297+ if (execv(TEST_REGISTRY_PATH "/scoperegistry", const_cast<char* const*>(args)) < 0)
1298+ {
1299+ perror("Error starting scoperegistry:");
1300+ }
1301+ return 1;
1302+ }
1303+ else if (rpid > 0)
1304+ {
1305+ rc = RUN_ALL_TESTS();
1306+ kill(rpid, SIGTERM);
1307+ waitpid(rpid, nullptr, 0);
1308+ }
1309+ else
1310+ {
1311+ perror("Failed to fork:");
1312+ }
1313+
1314+ return rc;
1315+}
1316
1317=== added file 'test/gtest/scopes/LoopDetection/LoopScope.cpp'
1318--- test/gtest/scopes/LoopDetection/LoopScope.cpp 1970-01-01 00:00:00 +0000
1319+++ test/gtest/scopes/LoopDetection/LoopScope.cpp 2015-01-16 00:25:35 +0000
1320@@ -0,0 +1,177 @@
1321+/*
1322+ * Copyright (C) 2014 Canonical Ltd
1323+ *
1324+ * This program is free software: you can redistribute it and/or modify
1325+ * it under the terms of the GNU Lesser General Public License version 3 as
1326+ * published by the Free Software Foundation.
1327+ *
1328+ * This program is distributed in the hope that it will be useful,
1329+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1330+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1331+ * GNU Lesser General Public License for more details.
1332+ *
1333+ * You should have received a copy of the GNU Lesser General Public License
1334+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1335+ *
1336+ * Authored by: Michi Henning <michi.henning@canonical.com>
1337+ */
1338+
1339+#include "LoopScope.h"
1340+
1341+#include <unity/scopes/CategorisedResult.h>
1342+#include <unity/scopes/ScopeBase.h>
1343+#include <unity/scopes/ScopeExceptions.h>
1344+#include <unity/scopes/SearchReply.h>
1345+
1346+#include <unity/UnityExceptions.h>
1347+#include <unity/util/FileIO.h>
1348+
1349+#include <mutex>
1350+#include <sstream>
1351+#include <thread>
1352+
1353+using namespace std;
1354+using namespace unity::scopes;
1355+
1356+namespace
1357+{
1358+
1359+class Receiver: public SearchListenerBase
1360+{
1361+public:
1362+
1363+ virtual void push(Category::SCPtr const& category) override
1364+ {
1365+ try
1366+ {
1367+ upstream_->register_category(category);
1368+ }
1369+ catch (unity::InvalidArgumentException const&)
1370+ {
1371+ }
1372+ }
1373+
1374+ virtual void push(CategorisedResult result) override
1375+ {
1376+ cerr << scope_id_ << ": received result: " << result.uri() << ", " << result.title() << endl;
1377+ try
1378+ {
1379+ upstream_->push(std::move(result));
1380+ }
1381+ catch (const unity::InvalidArgumentException &e)
1382+ {
1383+ cerr << scope_id_ << ": error pushing result: " << e.what() << endl;
1384+ }
1385+ }
1386+
1387+ virtual void finished(CompletionDetails const& details) override
1388+ {
1389+ cerr << scope_id_ << ": subquery complete, status: " << to_string(details.status())
1390+ << ", msg: " << details.message() << endl;
1391+ }
1392+
1393+ Receiver(string const& scope_id, SearchReplyProxy const& upstream) :
1394+ scope_id_(scope_id),
1395+ upstream_(upstream)
1396+ {
1397+ }
1398+
1399+private:
1400+ string scope_id_;
1401+ SearchReplyProxy upstream_;
1402+};
1403+
1404+class TestQuery : public SearchQueryBase
1405+{
1406+public:
1407+ TestQuery(CannedQuery const& query,
1408+ SearchMetadata const& metadata,
1409+ string const& id,
1410+ RegistryProxy const& reg)
1411+ : SearchQueryBase(query, metadata)
1412+ , id_(id)
1413+ , registry_(reg)
1414+ {
1415+ }
1416+
1417+ virtual void cancelled() override
1418+ {
1419+ }
1420+
1421+ virtual void run(SearchReplyProxy const& reply) override
1422+ {
1423+ auto cat = reply->register_category(id_, "", "");
1424+ SearchListenerBase::SPtr receiver(new Receiver(id_, reply));
1425+
1426+ // Open the command file and, for each scope ID (one per line),
1427+ // send a subquery to that scope.
1428+ istringstream cmds(unity::util::read_text_file(string(TEST_RUNTIME_PATH) + "/" + id_ + ".cmd"));
1429+ string child_id;
1430+ while (getline(cmds, child_id))
1431+ {
1432+ if (child_id.empty())
1433+ {
1434+ continue; // Ignore empty ID, which is what we get if we act as a leaf scope.
1435+ }
1436+ auto proxy = registry_->get_metadata(child_id).proxy();
1437+ cerr << id_ << ": sending subsearch to " << child_id << endl;
1438+ subsearch(proxy, id_ + "->" + child_id, receiver);
1439+ }
1440+ CategorisedResult res(cat);
1441+ res.set_uri("uri");
1442+ res.set_title(id_ + ": result for query \"" + query().query_string() + "\"");
1443+ if (valid())
1444+ {
1445+ reply->push(res);
1446+ }
1447+ }
1448+
1449+private:
1450+ string id_;
1451+ RegistryProxy registry_;
1452+};
1453+
1454+} // namespace
1455+
1456+void LoopScope::start(string const& scope_id)
1457+{
1458+ lock_guard<mutex> lock(mutex_);
1459+ id_ = scope_id;
1460+}
1461+
1462+void LoopScope::stop()
1463+{
1464+}
1465+
1466+void LoopScope::run()
1467+{
1468+}
1469+
1470+SearchQueryBase::UPtr LoopScope::search(CannedQuery const& query, SearchMetadata const& metadata)
1471+{
1472+ lock_guard<mutex> lock(mutex_);
1473+ return SearchQueryBase::UPtr(new TestQuery(query, metadata, id_, registry()));
1474+}
1475+
1476+PreviewQueryBase::UPtr LoopScope::preview(Result const&, ActionMetadata const &)
1477+{
1478+ return nullptr; // unused
1479+}
1480+
1481+extern "C"
1482+{
1483+
1484+ unity::scopes::ScopeBase*
1485+ // cppcheck-suppress unusedFunction
1486+ UNITY_SCOPE_CREATE_FUNCTION()
1487+ {
1488+ return new LoopScope;
1489+ }
1490+
1491+ void
1492+ // cppcheck-suppress unusedFunction
1493+ UNITY_SCOPE_DESTROY_FUNCTION(unity::scopes::ScopeBase* scope_base)
1494+ {
1495+ delete scope_base;
1496+ }
1497+}
1498
1499=== added file 'test/gtest/scopes/LoopDetection/LoopScope.h'
1500--- test/gtest/scopes/LoopDetection/LoopScope.h 1970-01-01 00:00:00 +0000
1501+++ test/gtest/scopes/LoopDetection/LoopScope.h 2015-01-16 00:25:35 +0000
1502@@ -0,0 +1,41 @@
1503+/*
1504+ * Copyright (C) 2014 Canonical Ltd
1505+ *
1506+ * This program is free software: you can redistribute it and/or modify
1507+ * it under the terms of the GNU Lesser General Public License version 3 as
1508+ * published by the Free Software Foundation.
1509+ *
1510+ * This program is distributed in the hope that it will be useful,
1511+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1512+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1513+ * GNU Lesser General Public License for more details.
1514+ *
1515+ * You should have received a copy of the GNU Lesser General Public License
1516+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1517+ *
1518+ * Authored by: Michi Henning <michi.henning@canonical.com>
1519+ */
1520+
1521+#pragma once
1522+
1523+#include <unity/scopes/ScopeBase.h>
1524+
1525+class LoopScope : public unity::scopes::ScopeBase
1526+{
1527+public:
1528+ virtual void start(std::string const&) override;
1529+
1530+ virtual void stop() override;
1531+
1532+ virtual void run() override;
1533+
1534+ virtual unity::scopes::SearchQueryBase::UPtr search(unity::scopes::CannedQuery const &,
1535+ unity::scopes::SearchMetadata const &) override;
1536+
1537+ virtual unity::scopes::PreviewQueryBase::UPtr preview(unity::scopes::Result const&,
1538+ unity::scopes::ActionMetadata const &) override;
1539+
1540+private:
1541+ std::string id_;
1542+ std::mutex mutex_;
1543+};
1544
1545=== added file 'test/gtest/scopes/LoopDetection/LoopScope.ini.in'
1546--- test/gtest/scopes/LoopDetection/LoopScope.ini.in 1970-01-01 00:00:00 +0000
1547+++ test/gtest/scopes/LoopDetection/LoopScope.ini.in 2015-01-16 00:25:35 +0000
1548@@ -0,0 +1,4 @@
1549+[ScopeConfig]
1550+DisplayName = LoopScope
1551+Description = Aggregator scope that makes various loops
1552+Author = Michi
1553
1554=== added file 'test/gtest/scopes/LoopDetection/Runtime.ini.in'
1555--- test/gtest/scopes/LoopDetection/Runtime.ini.in 1970-01-01 00:00:00 +0000
1556+++ test/gtest/scopes/LoopDetection/Runtime.ini.in 2015-01-16 00:25:35 +0000
1557@@ -0,0 +1,6 @@
1558+[Runtime]
1559+Registry.Identity = TestRegistry
1560+Registry.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/TestRegistry.ini
1561+Default.Middleware = Zmq
1562+Zmq.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/Zmq.ini
1563+Smartscopes.Registry.Identity =
1564
1565=== added file 'test/gtest/scopes/LoopDetection/TestRegistry.ini.in'
1566--- test/gtest/scopes/LoopDetection/TestRegistry.ini.in 1970-01-01 00:00:00 +0000
1567+++ test/gtest/scopes/LoopDetection/TestRegistry.ini.in 2015-01-16 00:25:35 +0000
1568@@ -0,0 +1,7 @@
1569+[Registry]
1570+Middleware = Zmq
1571+Zmq.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/Zmq.ini
1572+Scope.InstallDir = @CMAKE_CURRENT_BINARY_DIR@/scopes
1573+OEM.InstallDir = /unused
1574+Click.InstallDir = @CMAKE_CURRENT_BINARY_DIR@/click
1575+Scoperunner.Path = @PROJECT_BINARY_DIR@/scoperunner/scoperunner
1576
1577=== added file 'test/gtest/scopes/LoopDetection/Zmq.ini.in'
1578--- test/gtest/scopes/LoopDetection/Zmq.ini.in 1970-01-01 00:00:00 +0000
1579+++ test/gtest/scopes/LoopDetection/Zmq.ini.in 2015-01-16 00:25:35 +0000
1580@@ -0,0 +1,2 @@
1581+[Zmq]
1582+EndpointDir = /tmp
1583
1584=== modified file 'test/gtest/scopes/internal/JsonSettingsSchema/JsonSettingsSchema_test.cpp'
1585--- test/gtest/scopes/internal/JsonSettingsSchema/JsonSettingsSchema_test.cpp 2014-11-20 21:33:33 +0000
1586+++ test/gtest/scopes/internal/JsonSettingsSchema/JsonSettingsSchema_test.cpp 2015-01-16 00:25:35 +0000
1587@@ -397,7 +397,7 @@
1588 {
1589 "id": "x",
1590 "type": "number",
1591- "displayName": "X",
1592+ "displayName": "X",
1593 "parameters": {
1594 "defaultValue": "banana"
1595 }
1596@@ -457,7 +457,7 @@
1597 }
1598 catch (ResourceException const& e)
1599 {
1600- EXPECT_STREQ("unity::ResourceException: JsonSettingsSchema(): missing \"parameters\" definition, id = \"x\"",
1601+ EXPECT_STREQ("unity::ResourceException: JsonSettingsSchema(): missing \"displayName\" definition, id = \"x\"",
1602 e.what());
1603 }
1604
1605
1606=== modified file 'test/gtest/scopes/internal/zmq_middleware/ZmqMiddleware/ZmqMiddleware_test.cpp'
1607--- test/gtest/scopes/internal/zmq_middleware/ZmqMiddleware/ZmqMiddleware_test.cpp 2014-08-12 13:26:35 +0000
1608+++ test/gtest/scopes/internal/zmq_middleware/ZmqMiddleware/ZmqMiddleware_test.cpp 2015-01-16 00:25:35 +0000
1609@@ -421,6 +421,7 @@
1610
1611 virtual MWQueryCtrlProxy search(CannedQuery const&,
1612 SearchMetadata const&,
1613+ VariantMap const&,
1614 MWReplyProxy const&,
1615 InvokeInfo const&) override
1616 {
1617
1618=== modified file 'test/gtest/scopes/testing/IsolatedScope/IsolatedScope_test.cpp'
1619--- test/gtest/scopes/testing/IsolatedScope/IsolatedScope_test.cpp 2014-12-03 10:01:38 +0000
1620+++ test/gtest/scopes/testing/IsolatedScope/IsolatedScope_test.cpp 2015-01-16 00:25:35 +0000
1621@@ -211,7 +211,7 @@
1622 &queryctrl, [](unity::scopes::QueryCtrl*) {});
1623
1624 auto push_childscope_results =
1625- [queryctrl_proxy](Unused, Unused, unity::scopes::SearchListenerBase::SPtr const& listener) -> unity::scopes::QueryCtrlProxy {
1626+ [queryctrl_proxy](Unused, Unused, Unused, Unused, unity::scopes::SearchListenerBase::SPtr const& listener) -> unity::scopes::QueryCtrlProxy {
1627 unity::scopes::Category::SCPtr cat(
1628 new unity::scopes::testing::Category(
1629 "id", "title", "icon", unity::scopes::CategoryRenderer()));
1630@@ -229,7 +229,7 @@
1631 };
1632
1633 NiceMock<unity::scopes::testing::MockScope> childscope;
1634- EXPECT_CALL(childscope, search(aggregator_query, _, _))
1635+ EXPECT_CALL(childscope, search(aggregator_query, _, _, _, _))
1636 .WillOnce(Invoke(push_childscope_results));
1637 unity::scopes::ScopeProxy childscope_proxy(
1638 &childscope, [](unity::scopes::Scope*) {});

Subscribers

People subscribed via source and target branches

to all changes: