Merge lp:~michihenning/unity-scopes-api/scope-exceptions into lp:unity-scopes-api

Proposed by Michi Henning
Status: Merged
Approved by: Paweł Stołowski
Approved revision: 285
Merged at revision: 306
Proposed branch: lp:~michihenning/unity-scopes-api/scope-exceptions
Merge into: lp:unity-scopes-api
Diff against target: 1442 lines (+1019/-126)
19 files modified
debian/libunity-scopes3.symbols (+1/-1)
include/unity/scopes/internal/QueryObject.h (+1/-1)
include/unity/scopes/internal/ReplyObject.h (+0/-1)
include/unity/scopes/internal/ScopeObject.h (+1/-0)
src/scopes/QueryBase.cpp (+1/-14)
src/scopes/internal/ActivationQueryObject.cpp (+8/-6)
src/scopes/internal/PreviewQueryObject.cpp (+26/-7)
src/scopes/internal/QueryObject.cpp (+38/-12)
src/scopes/internal/ReplyObject.cpp (+16/-14)
src/scopes/internal/ScopeObject.cpp (+81/-70)
test/gtest/scopes/CMakeLists.txt (+1/-0)
test/gtest/scopes/ThrowingScope/CMakeLists.txt (+26/-0)
test/gtest/scopes/ThrowingScope/Runtime.ini.in (+7/-0)
test/gtest/scopes/ThrowingScope/TestRegistry.ini.in (+7/-0)
test/gtest/scopes/ThrowingScope/ThrowingScope.cpp (+273/-0)
test/gtest/scopes/ThrowingScope/ThrowingScope.h (+49/-0)
test/gtest/scopes/ThrowingScope/ThrowingScope.ini.in (+4/-0)
test/gtest/scopes/ThrowingScope/ThrowingScope_test.cpp (+477/-0)
test/gtest/scopes/ThrowingScope/Zmq.ini.in (+2/-0)
To merge this branch: bzr merge lp:~michihenning/unity-scopes-api/scope-exceptions
Reviewer Review Type Date Requested Status
Paweł Stołowski (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+246968@code.launchpad.net

Commit message

Fixed core dump in the run time when a scope threw an exception from PreviewQueryBase::run().

Added tests for the various scope methods to make sure that they are exception-safe.

Improved logging and error reporting for throwing scopes.

Description of the change

This MR fixes a core dump in the run time when a scope threw an exception from PreviewQueryBase::run().

Added tests for the various scope methods to make sure that they are exception-safe.

Improved logging and error reporting for throwing scopes.

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
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: Needs Fixing (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: Needs Fixing (continuous-integration)
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: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Looks good, thanks!

review: Approve
Revision history for this message
Paweł Stołowski (stolowski) wrote :
Download full text (13.0 KiB)

After resolving conflicts and attempting to build it with some other big branches (such as loop-prevention) we were seeing new kind of test failures in the silo, so I had to pull that branch off for the moment.

Here is the log:
https://launchpadlibrarian.net/195924595/buildlog_ubuntu-vivid-ppc64el.unity-scopes-api_0.6.12%2B15.04.20150127.1-0ubuntu1_FAILEDTOBUILD.txt.gz

In case the full log disappears, here is an excerpt of failures:

      Start 68: ThrowingScope

68: Test command: /build/buildd/unity-scopes-api-0.6.12+15.04.20150127.1/obj-powerpc64le-linux-gnu/test/gtest/scopes/ThrowingScope/ThrowingScope_test
68: Test timeout computed to be: 1500
68: [==========] Running 11 tests from 1 test case.
68: [----------] Global test environment set-up.
68: [----------] 11 tests from ThrowingScopeTest
68: [ RUN ] ThrowingScopeTest.no_error
68: scoperegistry [ThrowingScope_test]: unity::FileException: cannot open scope installation directory "/unused": No such file or directory (errno = 2)
68: scoperegistry [ThrowingScope_test]: could not open OEM installation directory, ignoring OEM scopes
68: scoperegistry [ThrowingScope_test]: unity::FileException: cannot open scope installation directory "/build/buildd/unity-scopes-api-0.6.12+15.04.20150127.1/obj-powerpc64le-linux-gnu/test/gtest/scopes/ThrowingScope/click": No such file or directory (errno = 2)
68: scoperegistry [ThrowingScope_test]: could not open Click installation directory, ignoring Click scopes
68: scoperegistry [ThrowingScope_test]: no remote registry configured, only local scopes will be available
68: [2015-01-27 09:06:51.739966] INFO: TestRegistry: ScopesWatcher: scope: "ThrowingScope" installed to: "/build/buildd/unity-scopes-api-0.6.12+15.04.20150127.1/obj-powerpc64le-linux-gnu/test/gtest/scopes/ThrowingScope/scopes/ThrowingScope"
68: [2015-01-27 09:06:51.772695] INFO: TestRegistry: RegistryObject::ScopeProcess::exec(): Process for scope: "ThrowingScope" started
68: [ OK ] ThrowingScopeTest.no_error (60 ms)
68: [ RUN ] ThrowingScopeTest.throw_from_search
68: [2015-01-27 09:06:51.781175] ERROR: ThrowingScope: Scope "ThrowingScope" threw an exception from search()
68: [ OK ] ThrowingScopeTest.throw_from_search (5 ms)
68: [ RUN ] ThrowingScopeTest.throw_from_run
68: [2015-01-27 09:06:51.786344] ERROR: ThrowingScope: QueryBase::run(): unity::ResourceException: exception from run
68: [2015-01-27 09:06:51.787049] WARNING: c-4e02aa2d00000000: ObjectAdapter: no servant for oneway request (id: f1dd504500000000, adapter: c-4e02aa2d00000000-r, op: finished)
68: [ OK ] ThrowingScopeTest.throw_from_run (5 ms)
68: [ RUN ] ThrowingScopeTest.throw_from_cancelled
68: [2015-01-27 09:06:52.791321] ERROR: ThrowingScope: QueryBase::cancelled(): unity::ResourceException: exception from cancelled
68: [2015-01-27 09:06:52.791663] WARNING: ThrowingScope: ObjectAdapter: no servant for oneway request (id: f82c2eee00000004, adapter: ThrowingScope-c, op: destroy)
68: [2015-01-27 09:06:52.791880] WARNING: c-4ce6aad900000000: ObjectAdapter: no servant for oneway request (id: 7e69520600000000, adapter: c-4ce6aad900000000-r, op: finished)
68: [2015-01-27 09:06:52.792170] WARNIN...

review: Needs Fixing
283. By Michi Henning

Merged trunk and resolved conflicts.

284. By Michi Henning

Removed trailing whitespace.

285. By Michi Henning

Fixed race in test that caused bogus test failure if exception
from cancelled wasn't processed by the time run() returned. Fixed
race in ReplyObject. (info_list_ was accessed whithout holding a lock.)

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

The failure that Pawel saw was caused by the merge having omitted a change from my branch. While testing, I found a race in the test that caused a bogus failure, and also fixed a real race in ReplyObject. Should be good now.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/libunity-scopes3.symbols'
2--- debian/libunity-scopes3.symbols 2015-01-30 12:34:39 +0000
3+++ debian/libunity-scopes3.symbols 2015-02-04 05:47:34 +0000
4@@ -546,7 +546,7 @@
5 (c++)"unity::scopes::internal::ScopeObject::debug_mode() const@Base" 0.6.2+rtm+rtm+rtm+14.09.20140818
6 (c++)"unity::scopes::internal::ScopeObject::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
7 (c++)"unity::scopes::internal::ScopeObject::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
8- (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
9+ (c++)"unity::scopes::internal::ScopeObject::query(std::shared_ptr<unity::scopes::internal::MWReply> const&, unity::scopes::internal::MiddlewareBase*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, 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" 0replaceme
10 (c++)"unity::scopes::internal::ScopeObject::~ScopeObject()@Base" 0.4.0+14.04.20140312.1
11 (c++)"unity::scopes::internal::ScopeObject::ScopeObject(unity::scopes::ScopeBase*, bool)@Base" 0.6.12+15.04.20150127.2
12 (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" 0.6.12+15.04.20150127.2
13
14=== modified file 'include/unity/scopes/internal/QueryObject.h'
15--- include/unity/scopes/internal/QueryObject.h 2015-01-09 00:14:14 +0000
16+++ include/unity/scopes/internal/QueryObject.h 2015-02-04 05:47:34 +0000
17@@ -67,7 +67,7 @@
18
19 protected:
20 std::shared_ptr<QueryBase> query_base_;
21- MWReplyProxy reply_;
22+ MWReplyProxy const reply_;
23 std::weak_ptr<unity::scopes::Reply> reply_proxy_;
24 MWQueryCtrlProxy const ctrl_;
25 bool pushable_;
26
27=== modified file 'include/unity/scopes/internal/ReplyObject.h'
28--- include/unity/scopes/internal/ReplyObject.h 2015-01-22 03:54:58 +0000
29+++ include/unity/scopes/internal/ReplyObject.h 2015-02-04 05:47:34 +0000
30@@ -69,7 +69,6 @@
31 std::condition_variable idle_;
32 std::string origin_proxy_;
33 int num_push_;
34- std::atomic_bool info_occurred_;
35 std::vector<OperationInfo> info_list_;
36 };
37
38
39=== modified file 'include/unity/scopes/internal/ScopeObject.h'
40--- include/unity/scopes/internal/ScopeObject.h 2015-01-26 08:20:45 +0000
41+++ include/unity/scopes/internal/ScopeObject.h 2015-02-04 05:47:34 +0000
42@@ -84,6 +84,7 @@
43
44 private:
45 MWQueryCtrlProxy query(MWReplyProxy const& reply, MiddlewareBase* mw_base,
46+ std::string const& method,
47 std::function<QueryBase::SPtr(void)> const& query_factory_fun,
48 std::function<QueryObjectBase::SPtr(QueryBase::SPtr, MWQueryCtrlProxy)> const& query_object_factory_fun);
49 ScopeBase* const scope_base_;
50
51=== modified file 'src/scopes/QueryBase.cpp'
52--- src/scopes/QueryBase.cpp 2014-08-05 06:42:04 +0000
53+++ src/scopes/QueryBase.cpp 2015-02-04 05:47:34 +0000
54@@ -43,20 +43,7 @@
55 void QueryBase::cancel()
56 {
57 p->cancel(); // Forward cancel to subqueries
58- try
59- {
60- cancelled(); // Inform this query that it was cancelled
61- }
62- catch (std::exception const& e)
63- {
64- cerr << "QueryBase::cancel(): exception from cancelled(): " << e.what() << endl;
65- // TODO: log error
66- }
67- catch (...)
68- {
69- cerr << "QueryBase::cancel(): unknown exception from cancelled()" << endl;
70- // TODO: log error
71- }
72+ cancelled();
73 }
74 /// @endcond
75
76
77=== modified file 'src/scopes/internal/ActivationQueryObject.cpp'
78--- src/scopes/internal/ActivationQueryObject.cpp 2015-01-09 00:14:14 +0000
79+++ src/scopes/internal/ActivationQueryObject.cpp 2015-02-04 05:47:34 +0000
80@@ -55,8 +55,8 @@
81 {
82 assert(self_);
83
84- // The reply proxy now holds our reference count high, so
85- // we can drop our own smart pointer and disconnect from the middleware.
86+ // Disconnect from middleware. While this request is in progress,
87+ // this instance will not be deallocated.
88 self_ = nullptr;
89 disconnect();
90
91@@ -71,13 +71,15 @@
92 }
93 catch (std::exception const& e)
94 {
95- BOOST_LOG(info.mw->runtime()->logger()) << "ActivationQueryObject::run(): " << e.what();
96- reply_->finished(CompletionDetails(CompletionDetails::Error, e.what())); // Oneway, can't block
97+ BOOST_LOG(info.mw->runtime()->logger()) << "ActivationQueryBase::activate(): " << e.what();
98+ reply_->finished(CompletionDetails(CompletionDetails::Error,
99+ string("ActivationQueryBase::activate(): ") + e.what()));
100 }
101 catch (...)
102 {
103- BOOST_LOG(info.mw->runtime()->logger()) << "ActivationQueryObject::run(): unknown exception";
104- reply_->finished(CompletionDetails(CompletionDetails::Error, "unknown exception")); // Oneway, can't block
105+ BOOST_LOG(info.mw->runtime()->logger()) << "ActivationQueryBase::activate(): unknown exception";
106+ reply_->finished(CompletionDetails(CompletionDetails::Error,
107+ "ActivationQueryBase::activate(): unknown exception"));
108 }
109 }
110
111
112=== modified file 'src/scopes/internal/PreviewQueryObject.cpp'
113--- src/scopes/internal/PreviewQueryObject.cpp 2015-01-09 03:16:51 +0000
114+++ src/scopes/internal/PreviewQueryObject.cpp 2015-02-04 05:47:34 +0000
115@@ -57,6 +57,16 @@
116
117 void PreviewQueryObject::run(MWReplyProxy const& reply, InvokeInfo const& info) noexcept
118 {
119+ unique_lock<mutex> lock(mutex_);
120+
121+ // See comment in QueryObject::run()
122+ if (!pushable_)
123+ {
124+ self_ = nullptr;
125+ disconnect();
126+ return;
127+ }
128+
129 assert(self_);
130
131 auto reply_proxy = make_shared<PreviewReplyImpl>(reply, self_);
132@@ -68,24 +78,33 @@
133 self_ = nullptr;
134 disconnect();
135
136- // Synchronous call into scope implementation.
137- // On return, replies for the query may still be outstanding.
138 try
139 {
140+ lock.unlock();
141+
142+ // Synchronous call into scope implementation.
143+ // On return, replies for the preview may still be outstanding.
144 auto preview_query = dynamic_pointer_cast<PreviewQueryBase>(query_base_);
145 assert(preview_query);
146 preview_query->run(reply_proxy);
147 }
148 catch (std::exception const& e)
149 {
150- pushable_ = false;
151- BOOST_LOG(info.mw->runtime()->logger()) << "PreviewQueryObject::run(): " << e.what();
152- reply_->finished(CompletionDetails(CompletionDetails::Error, e.what())); // Oneway, can't block
153+ {
154+ lock_guard<mutex> lock(mutex_);
155+ pushable_ = false;
156+ }
157+ BOOST_LOG(info.mw->runtime()->logger()) << "PreviewQueryBase::run(): " << e.what();
158+ reply_->finished(CompletionDetails(CompletionDetails::Error, string("PreviewQueryBase::run(): ") + e.what()));
159 }
160 catch (...)
161 {
162- BOOST_LOG(info.mw->runtime()->logger()) << "PreviewQueryObject::run(): unknown exception";
163- reply_->finished(CompletionDetails(CompletionDetails::Error, "unknown exception")); // Oneway, can't block
164+ {
165+ lock_guard<mutex> lock(mutex_);
166+ pushable_ = false;
167+ }
168+ BOOST_LOG(info.mw->runtime()->logger()) << "PreviewQueryBase::run(): unknown exception";
169+ reply_->finished(CompletionDetails(CompletionDetails::Error, "PreviewQueryBase::run(): unknown exception"));
170 }
171 }
172
173
174=== modified file 'src/scopes/internal/QueryObject.cpp'
175--- src/scopes/internal/QueryObject.cpp 2015-01-09 03:16:51 +0000
176+++ src/scopes/internal/QueryObject.cpp 2015-02-04 05:47:34 +0000
177@@ -123,19 +123,25 @@
178 }
179 catch (std::exception const& e)
180 {
181- pushable_ = false;
182- BOOST_LOG(info.mw->runtime()->logger()) << "ScopeBase::run(): " << e.what();
183- reply_->finished(CompletionDetails(CompletionDetails::Error, e.what())); // Oneway, can't block
184+ {
185+ lock_guard<mutex> lock(mutex_);
186+ pushable_ = false;
187+ }
188+ BOOST_LOG(info.mw->runtime()->logger()) << "QueryBase::run(): " << e.what();
189+ reply_->finished(CompletionDetails(CompletionDetails::Error, string("QueryBase::run(): ") + e.what()));
190 }
191 catch (...)
192 {
193- pushable_ = false;
194- BOOST_LOG(info.mw->runtime()->logger()) << "ScopeBase::run(): unknown exception";
195- reply_->finished(CompletionDetails(CompletionDetails::Error, "unknown exception")); // Oneway, can't block
196+ {
197+ lock_guard<mutex> lock(mutex_);
198+ pushable_ = false;
199+ }
200+ BOOST_LOG(info.mw->runtime()->logger()) << "QueryBase::run(): unknown exception";
201+ reply_->finished(CompletionDetails(CompletionDetails::Error, "QueryBase::run(): unknown exception"));
202 }
203 }
204
205-void QueryObject::cancel(InvokeInfo const& /* info */)
206+void QueryObject::cancel(InvokeInfo const& info)
207 {
208 {
209 lock_guard<mutex> lock(mutex_);
210@@ -145,6 +151,13 @@
211 return;
212 }
213 pushable_ = false;
214+ } // Release lock
215+
216+ try
217+ {
218+ // Forward the cancellation to the query base (which in turn will forward it to any subqueries).
219+ // The query base also calls the cancelled() callback to inform the application code.
220+ query_base_->cancel();
221
222 auto rp = reply_proxy_.lock();
223 if (rp)
224@@ -154,11 +167,24 @@
225 // a CompletionDetails::CompletionStatus (whereas the public ReplyProxy does not).
226 reply_->finished(CompletionDetails(CompletionDetails::Cancelled)); // Oneway, can't block
227 }
228- } // Release lock
229-
230- // Forward the cancellation to the query base (which in turn will forward it to any subqueries).
231- // The query base also calls the cancelled() callback to inform the application code.
232- query_base_->cancel();
233+ }
234+ catch (std::exception const& e)
235+ {
236+ BOOST_LOG(info.mw->runtime()->logger()) << "QueryBase::cancelled(): " << e.what();
237+ // Deliberately no error completion here. On the client side, we short-cut the cancelled()
238+ // callback locally, which unregisters the servant for the cancel message and assumes that
239+ // cancellation will work, passing a Cancelled status, even if cancellation throws in the scope.
240+ // But, removing the servant may be slow, allowing the finished message below to occasionally
241+ // reach the client. If it does, we want the same completion status that we get if the
242+ // local short-cut call hasn't done the job yet.
243+ reply_->finished(CompletionDetails(CompletionDetails::Cancelled, ""));
244+ }
245+ catch (...)
246+ {
247+ BOOST_LOG(info.mw->runtime()->logger()) << "QueryBase::cancelled(): unknown exception";
248+ // Same caveat as for std::exception here.
249+ reply_->finished(CompletionDetails(CompletionDetails::Cancelled, ""));
250+ }
251 }
252
253 bool QueryObject::pushable(InvokeInfo const& /* info */) const noexcept
254
255=== modified file 'src/scopes/internal/ReplyObject.cpp'
256--- src/scopes/internal/ReplyObject.cpp 2014-12-10 02:05:26 +0000
257+++ src/scopes/internal/ReplyObject.cpp 2015-02-04 05:47:34 +0000
258@@ -40,19 +40,20 @@
259 namespace internal
260 {
261
262-ReplyObject::ReplyObject(ListenerBase::SPtr const& receiver_base, RuntimeImpl const* runtime,
263- std::string const& scope_proxy, bool dont_reap) :
264- runtime_(runtime),
265- listener_base_(receiver_base),
266- finished_(false),
267- origin_proxy_(scope_proxy),
268- num_push_(0),
269- info_occurred_(false)
270+ReplyObject::ReplyObject(ListenerBase::SPtr const& receiver_base,
271+ RuntimeImpl const* runtime,
272+ std::string const& scope_proxy,
273+ bool dont_reap)
274+ : runtime_(runtime)
275+ , listener_base_(receiver_base)
276+ , finished_(false)
277+ , origin_proxy_(scope_proxy)
278+ , num_push_(0)
279 {
280 assert(receiver_base);
281 assert(runtime);
282
283- if (dont_reap == false)
284+ if (!dont_reap)
285 {
286 unique_lock<mutex> lock(mutex_); // Make sure that when lambda fires, it sees current values.
287 reap_item_ = runtime->reply_reaper()->add([this] {
288@@ -134,12 +135,11 @@
289 // Safe to call finished now if something went wrong or cardinality was exceeded.
290 if (!error.empty())
291 {
292- // TODO: log error
293+ BOOST_LOG(runtime_->logger()) << "ReplyObject::push(): " << error;
294 finished(CompletionDetails(CompletionDetails::Error, error));
295 }
296 else if (stop)
297 {
298- // TODO: log error
299 finished(CompletionDetails(CompletionDetails::OK));
300 }
301 }
302@@ -171,7 +171,6 @@
303 unique_lock<mutex> lock(mutex_);
304 assert(num_push_ >= 0);
305 idle_.wait(lock, [this] { return num_push_ == 0; });
306- lock.unlock(); // Inform the application code that the query is complete outside synchronization.
307 try
308 {
309 CompletionDetails details_with_info(details);
310@@ -179,6 +178,7 @@
311 {
312 details_with_info.add_info(info);
313 }
314+ lock.unlock(); // Inform the application code that the query is complete outside synchronization.
315 listener_base_->finished(details_with_info);
316 }
317 catch (std::exception const& e)
318@@ -208,11 +208,13 @@
319 {
320 reap_item_->refresh();
321 }
322- info_occurred_.exchange(true);
323
324 try
325 {
326- info_list_.push_back(op_info);
327+ {
328+ unique_lock<mutex> lock(mutex_);
329+ info_list_.push_back(op_info);
330+ }
331 listener_base_->info(op_info);
332 }
333 catch (std::exception const& e)
334
335=== modified file 'src/scopes/internal/ScopeObject.cpp'
336--- src/scopes/internal/ScopeObject.cpp 2015-01-26 08:20:45 +0000
337+++ src/scopes/internal/ScopeObject.cpp 2015-02-04 05:47:34 +0000
338@@ -59,7 +59,9 @@
339 {
340 }
341
342-MWQueryCtrlProxy ScopeObject::query(MWReplyProxy const& reply, MiddlewareBase* mw_base,
343+MWQueryCtrlProxy ScopeObject::query(MWReplyProxy const& reply,
344+ MiddlewareBase* mw_base,
345+ std::string const& method,
346 std::function<QueryBase::SPtr()> const& query_factory_fun,
347 std::function<QueryObjectBase::SPtr(QueryBase::SPtr, MWQueryCtrlProxy)> const& query_object_factory_fun)
348 {
349@@ -70,7 +72,8 @@
350 // to be safe, we don't assert, in case someone is running a broken client.
351
352 // TODO: log error about incoming request containing an invalid reply proxy.
353- throw LogicException("Scope \"" + mw_base->runtime()->scope_id() + "\": query() called with null reply proxy");
354+ throw LogicException("Scope \"" + mw_base->runtime()->scope_id() + "\": "
355+ + method + " called with null reply proxy");
356 }
357
358 // Ask scope to instantiate a new query.
359@@ -80,7 +83,7 @@
360 query_base = query_factory_fun();
361 if (!query_base)
362 {
363- string msg = "Scope \"" + mw_base->runtime()->scope_id() + "\" returned nullptr from query_factory_fun()";
364+ string msg = "Scope \"" + mw_base->runtime()->scope_id() + "\" returned nullptr from " + method + "()";
365 BOOST_LOG(mw_base->runtime()->logger()) << msg;
366 throw ResourceException(msg);
367 }
368@@ -88,7 +91,7 @@
369 }
370 catch (...)
371 {
372- string msg = "Scope \"" + mw_base->runtime()->scope_id() + "\" threw an exception from query_factory_fun()";
373+ string msg = "Scope \"" + mw_base->runtime()->scope_id() + "\" threw an exception from " + method + "()";
374 BOOST_LOG(mw_base->runtime()->logger()) << msg;
375 throw ResourceException(msg);
376 }
377@@ -151,45 +154,47 @@
378 MWReplyProxy const& reply,
379 InvokeInfo const& info)
380 {
381- return query(reply, info.mw,
382- [&q, &hints, &context, this]() -> SearchQueryBase::UPtr {
383- auto search_query = this->scope_base_->search(q, hints);
384- search_query->set_department_id(q.department_id());
385-
386- auto sqb = dynamic_cast<SearchQueryBaseImpl*>(search_query->fwd());
387- assert(sqb);
388-
389- // Set client ID and history that we received in the SearchQueryBase
390- // for loop detection.
391- auto const c_it = context.find("client_id");
392- if (c_it != context.end())
393- {
394- string client_id;
395- client_id = c_it->second.get_string();
396- sqb->set_client_id(client_id);
397- }
398-
399- auto const h_it = context.find("history");
400- if (h_it != context.end())
401- {
402- auto const hlist = h_it->second.get_array();
403- SearchQueryBaseImpl::History history;
404- for (auto const& t : hlist)
405- {
406- string client_id = t.get_dict()["c"].get_string();
407- string agg = t.get_dict()["a"].get_string();
408- string recv = t.get_dict()["r"].get_string();
409- SearchQueryBaseImpl::HistoryData hd = make_tuple(client_id, agg, recv);
410- history.push_back(hd);
411- }
412- sqb->set_history(history);
413- }
414-
415- return search_query;
416- },
417- [&reply, &hints, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
418- return make_shared<QueryObject>(query_base, hints.cardinality(), reply, ctrl_proxy);
419- }
420+ return query(reply,
421+ info.mw,
422+ "search",
423+ [&q, &hints, &context, this]() -> SearchQueryBase::UPtr {
424+ auto search_query = this->scope_base_->search(q, hints);
425+ search_query->set_department_id(q.department_id());
426+
427+ auto sqb = dynamic_cast<SearchQueryBaseImpl*>(search_query->fwd());
428+ assert(sqb);
429+
430+ // Set client ID and history that we received in the SearchQueryBase
431+ // for loop detection.
432+ auto const c_it = context.find("client_id");
433+ if (c_it != context.end())
434+ {
435+ string client_id;
436+ client_id = c_it->second.get_string();
437+ sqb->set_client_id(client_id);
438+ }
439+
440+ auto const h_it = context.find("history");
441+ if (h_it != context.end())
442+ {
443+ auto const hlist = h_it->second.get_array();
444+ SearchQueryBaseImpl::History history;
445+ for (auto const& t : hlist)
446+ {
447+ string client_id = t.get_dict()["c"].get_string();
448+ string agg = t.get_dict()["a"].get_string();
449+ string recv = t.get_dict()["r"].get_string();
450+ SearchQueryBaseImpl::HistoryData hd = make_tuple(client_id, agg, recv);
451+ history.push_back(hd);
452+ }
453+ sqb->set_history(history);
454+ }
455+
456+ return search_query;
457+ },
458+ [&reply, &hints, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
459+ return make_shared<QueryObject>(query_base, hints.cardinality(), reply, ctrl_proxy);
460+ }
461 );
462 }
463
464@@ -198,15 +203,17 @@
465 MWReplyProxy const& reply,
466 InvokeInfo const& info)
467 {
468- return query(reply, info.mw,
469- [&result, &hints, this]() -> QueryBase::SPtr {
470- return this->scope_base_->activate(result, hints);
471- },
472- [&reply, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
473- auto activation_base = dynamic_pointer_cast<ActivationQueryBase>(query_base);
474- assert(activation_base);
475- return make_shared<ActivationQueryObject>(activation_base, reply, ctrl_proxy);
476- }
477+ return query(reply,
478+ info.mw,
479+ "activate",
480+ [&result, &hints, this]() -> QueryBase::SPtr {
481+ return this->scope_base_->activate(result, hints);
482+ },
483+ [&reply, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
484+ auto activation_base = dynamic_pointer_cast<ActivationQueryBase>(query_base);
485+ assert(activation_base);
486+ return make_shared<ActivationQueryObject>(activation_base, reply, ctrl_proxy);
487+ }
488 );
489 }
490
491@@ -217,15 +224,17 @@
492 MWReplyProxy const &reply,
493 InvokeInfo const& info)
494 {
495- return query(reply, info.mw,
496- [&result, &hints, &widget_id, &action_id, this]() -> QueryBase::SPtr {
497- return this->scope_base_->perform_action(result, hints, widget_id, action_id);
498- },
499- [&reply, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
500- auto activation_base = dynamic_pointer_cast<ActivationQueryBase>(query_base);
501- assert(activation_base);
502- return make_shared<ActivationQueryObject>(activation_base, reply, ctrl_proxy);
503- }
504+ return query(reply,
505+ info.mw,
506+ "perform_action",
507+ [&result, &hints, &widget_id, &action_id, this]() -> QueryBase::SPtr {
508+ return this->scope_base_->perform_action(result, hints, widget_id, action_id);
509+ },
510+ [&reply, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
511+ auto activation_base = dynamic_pointer_cast<ActivationQueryBase>(query_base);
512+ assert(activation_base);
513+ return make_shared<ActivationQueryObject>(activation_base, reply, ctrl_proxy);
514+ }
515 );
516 }
517
518@@ -234,15 +243,17 @@
519 MWReplyProxy const& reply,
520 InvokeInfo const& info)
521 {
522- return query(reply, info.mw,
523- [&result, &hints, this]() -> QueryBase::SPtr {
524- return this->scope_base_->preview(result, hints);
525- },
526- [&reply, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
527- auto preview_query = dynamic_pointer_cast<PreviewQueryBase>(query_base);
528- assert(preview_query);
529- return make_shared<PreviewQueryObject>(preview_query, reply, ctrl_proxy);
530- }
531+ return query(reply,
532+ info.mw,
533+ "preview",
534+ [&result, &hints, this]() -> QueryBase::SPtr {
535+ return this->scope_base_->preview(result, hints);
536+ },
537+ [&reply, this](QueryBase::SPtr query_base, MWQueryCtrlProxy ctrl_proxy) -> QueryObjectBase::SPtr {
538+ auto preview_query = dynamic_pointer_cast<PreviewQueryBase>(query_base);
539+ assert(preview_query);
540+ return make_shared<PreviewQueryObject>(preview_query, reply, ctrl_proxy);
541+ }
542 );
543 }
544
545
546=== modified file 'test/gtest/scopes/CMakeLists.txt'
547--- test/gtest/scopes/CMakeLists.txt 2015-01-26 08:20:45 +0000
548+++ test/gtest/scopes/CMakeLists.txt 2015-02-04 05:47:34 +0000
549@@ -35,6 +35,7 @@
550 add_subdirectory(Runtime)
551 add_subdirectory(ScopeBase)
552 add_subdirectory(ScopeExceptions)
553+add_subdirectory(ThrowingScope)
554 add_subdirectory(Variant)
555 add_subdirectory(VariantBuilder)
556 add_subdirectory(Version)
557
558=== added directory 'test/gtest/scopes/ThrowingScope'
559=== added file 'test/gtest/scopes/ThrowingScope/CMakeLists.txt'
560--- test/gtest/scopes/ThrowingScope/CMakeLists.txt 1970-01-01 00:00:00 +0000
561+++ test/gtest/scopes/ThrowingScope/CMakeLists.txt 2015-02-04 05:47:34 +0000
562@@ -0,0 +1,26 @@
563+configure_file(TestRegistry.ini.in ${CMAKE_CURRENT_BINARY_DIR}/TestRegistry.ini)
564+configure_file(Runtime.ini.in ${CMAKE_CURRENT_BINARY_DIR}/Runtime.ini)
565+configure_file(Zmq.ini.in ${CMAKE_CURRENT_BINARY_DIR}/Zmq.ini)
566+
567+add_definitions(-DTEST_RUNTIME_PATH="${CMAKE_CURRENT_BINARY_DIR}")
568+add_definitions(-DTEST_RUNTIME_FILE="${CMAKE_CURRENT_BINARY_DIR}/Runtime.ini")
569+add_definitions(-DTEST_REGISTRY_PATH="${PROJECT_BINARY_DIR}/scoperegistry")
570+
571+add_executable(ThrowingScope_test ThrowingScope_test.cpp ThrowingScope.cpp)
572+target_link_libraries(ThrowingScope_test ${TESTLIBS})
573+
574+add_dependencies(ThrowingScope_test scoperegistry scoperunner)
575+
576+add_test(ThrowingScope ThrowingScope_test)
577+
578+set(SCOPE_DIR "${CMAKE_CURRENT_BINARY_DIR}/scopes")
579+
580+foreach (scope ThrowingScope)
581+ file(MAKE_DIRECTORY "${SCOPE_DIR}/${scope}")
582+ configure_file(ThrowingScope.ini.in ${SCOPE_DIR}/${scope}/${scope}.ini)
583+ add_library(${scope} MODULE ThrowingScope.cpp)
584+ set_target_properties(${scope}
585+ PROPERTIES
586+ LIBRARY_OUTPUT_DIRECTORY "${SCOPE_DIR}/${scope}/"
587+ )
588+endforeach()
589
590=== added file 'test/gtest/scopes/ThrowingScope/Runtime.ini.in'
591--- test/gtest/scopes/ThrowingScope/Runtime.ini.in 1970-01-01 00:00:00 +0000
592+++ test/gtest/scopes/ThrowingScope/Runtime.ini.in 2015-02-04 05:47:34 +0000
593@@ -0,0 +1,7 @@
594+[Runtime]
595+Registry.Identity = TestRegistry
596+Registry.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/TestRegistry.ini
597+Default.Middleware = Zmq
598+Zmq.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/Zmq.ini
599+Smartscopes.Registry.Identity =
600+LogDir=
601
602=== added file 'test/gtest/scopes/ThrowingScope/TestRegistry.ini.in'
603--- test/gtest/scopes/ThrowingScope/TestRegistry.ini.in 1970-01-01 00:00:00 +0000
604+++ test/gtest/scopes/ThrowingScope/TestRegistry.ini.in 2015-02-04 05:47:34 +0000
605@@ -0,0 +1,7 @@
606+[Registry]
607+Middleware = Zmq
608+Zmq.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/Zmq.ini
609+Scope.InstallDir = @CMAKE_CURRENT_BINARY_DIR@/scopes
610+OEM.InstallDir = /unused
611+Click.InstallDir = @CMAKE_CURRENT_BINARY_DIR@/click
612+Scoperunner.Path = @PROJECT_BINARY_DIR@/scoperunner/scoperunner
613
614=== added file 'test/gtest/scopes/ThrowingScope/ThrowingScope.cpp'
615--- test/gtest/scopes/ThrowingScope/ThrowingScope.cpp 1970-01-01 00:00:00 +0000
616+++ test/gtest/scopes/ThrowingScope/ThrowingScope.cpp 2015-02-04 05:47:34 +0000
617@@ -0,0 +1,273 @@
618+/*
619+ * Copyright (C) 2015 Canonical Ltd
620+ *
621+ * This program is free software: you can redistribute it and/or modify
622+ * it under the terms of the GNU Lesser General Public License version 3 as
623+ * published by the Free Software Foundation.
624+ *
625+ * This program is distributed in the hope that it will be useful,
626+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
627+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
628+ * GNU Lesser General Public License for more details.
629+ *
630+ * You should have received a copy of the GNU Lesser General Public License
631+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
632+ *
633+ * Authored by: Michi Henning <michi.henning@canonical.com>
634+ */
635+
636+#include "ThrowingScope.h"
637+
638+#include <unity/scopes/ActionMetadata.h>
639+#include <unity/scopes/CategorisedResult.h>
640+#include <unity/scopes/ScopeBase.h>
641+#include <unity/scopes/SearchReply.h>
642+
643+#include <unity/UnityExceptions.h>
644+#include <unity/util/FileIO.h>
645+
646+#include <condition_variable>
647+#include <mutex>
648+#include <thread>
649+
650+using namespace std;
651+using namespace unity;
652+using namespace unity::scopes;
653+
654+namespace
655+{
656+
657+class TestQuery : public SearchQueryBase
658+{
659+public:
660+ TestQuery(CannedQuery const& query,
661+ SearchMetadata const& metadata,
662+ string const& id)
663+ : SearchQueryBase(query, metadata)
664+ , id_(id)
665+ , query_cancelled_(false)
666+ {
667+ }
668+
669+ virtual void cancelled() override
670+ {
671+ if (query().query_string() == "throw from cancelled")
672+ {
673+ lock_guard<mutex> lock(mutex_);
674+ query_cancelled_ = true;
675+ cond_.notify_all();
676+ throw ResourceException("exception from cancelled");
677+ }
678+ }
679+
680+ virtual void run(SearchReplyProxy const& reply) override
681+ {
682+ string query_string = query().query_string();
683+ if (query_string == "throw from run")
684+ {
685+ throw ResourceException("exception from run");
686+ }
687+ else if (query_string == "throw from cancelled")
688+ {
689+ unique_lock<mutex> lock(mutex_);
690+ cond_.wait(lock, [this] { return this->query_cancelled_; });
691+ return;
692+ }
693+ auto cat = reply->register_category(id_, "", "");
694+ CategorisedResult res(cat);
695+ res.set_uri("uri");
696+ res.set_title(query_string);
697+ res.set_intercept_activation();
698+ if (valid())
699+ {
700+ reply->push(res);
701+ }
702+ // Need to wait a while here, to make sure that the exception thrown from cancelled()
703+ // is picked up by the runtime and the cancelled message is sent back to the client.
704+ // If we don't do this, a successful completion message will be sent as soon as
705+ // we return from here, and that might happen before the runtime has managed to send the
706+ // cancelled message, causing the test to fail because then we see successful
707+ // completion instead of cancelled completion.
708+ this_thread::sleep_for(chrono::milliseconds(200));
709+ }
710+
711+private:
712+ string id_;
713+ bool query_cancelled_;
714+ mutex mutex_;
715+ condition_variable cond_;
716+};
717+
718+class TestPreview : public PreviewQueryBase
719+{
720+public:
721+ TestPreview(Result const& result, ActionMetadata const& metadata)
722+ : PreviewQueryBase(result, metadata)
723+ , result_(result)
724+ , preview_cancelled_(false)
725+ {
726+ }
727+
728+ virtual void cancelled() override
729+ {
730+ if (result_.title() == "throw from preview cancelled")
731+ {
732+ lock_guard<mutex> lock(mutex_);
733+ preview_cancelled_ = true;
734+ cond_.notify_all();
735+ throw ResourceException("exception from preview cancelled");
736+ }
737+ }
738+
739+ virtual void run(PreviewReplyProxy const&) override
740+ {
741+ if (result_.title() == "throw from preview run")
742+ {
743+ throw ResourceException(result_.title());
744+ }
745+ else if (result_.title() == "throw from preview cancelled")
746+ {
747+ unique_lock<mutex> lock(mutex_);
748+ cond_.wait(lock, [this] { return this->preview_cancelled_; });
749+ // Need to wait a while here, to make sure that the exception thrown from cancelled()
750+ // is picked up by the runtime and the cancelled message is sent back to the client.
751+ // If we don't do this, a successful completion message will be sent as soon as
752+ // we return from here, and that might happen before the runtime has managed to send the
753+ // cancelled message, causing the test to fail because then we see successful
754+ // completion instead of cancelled completion.
755+ this_thread::sleep_for(chrono::milliseconds(200));
756+ }
757+ }
758+
759+private:
760+ Result result_;
761+ bool preview_cancelled_;
762+ mutex mutex_;
763+ condition_variable cond_;
764+};
765+
766+class TestActivation : public ActivationQueryBase
767+{
768+public:
769+ TestActivation(Result const& result, ActionMetadata const& metadata)
770+ : ActivationQueryBase(result, metadata)
771+ , result_(result)
772+ , activate_cancelled_(false)
773+ {
774+ }
775+
776+ virtual void cancelled() override
777+ {
778+ if (result_.title() == "throw from activation cancelled")
779+ {
780+ lock_guard<mutex> lock(mutex_);
781+ activate_cancelled_ = true;
782+ cond_.notify_all();
783+ throw ResourceException("exception from activation cancelled");
784+ }
785+ }
786+
787+ virtual ActivationResponse activate() override
788+ {
789+ if (result_.title() == "throw from activation activate")
790+ {
791+ throw ResourceException(result_.title());
792+ }
793+ else if (result_.title() == "throw from activation cancelled")
794+ {
795+ unique_lock<mutex> lock(mutex_);
796+ cond_.wait(lock, [this] { return this->activate_cancelled_; });
797+ // Need to wait a while here, to make sure that the exception thrown from cancelled()
798+ // is picked up by the runtime and the cancelled message is sent back to the client.
799+ // If we don't do this, a successful completion message will be sent as soon as
800+ // we return from here, and that might happen before the runtime has managed to send the
801+ // cancelled message, causing the test to fail because then we see successful
802+ // completion instead of cancelled completion.
803+ this_thread::sleep_for(chrono::milliseconds(200));
804+ }
805+ return ActivationResponse(ActivationResponse::NotHandled);
806+ }
807+
808+private:
809+ Result result_;
810+ bool activate_cancelled_;
811+ mutex mutex_;
812+ condition_variable cond_;
813+};
814+
815+} // namespace
816+
817+void ThrowingScope::start(string const& scope_id)
818+{
819+ lock_guard<mutex> lock(mutex_);
820+ id_ = scope_id;
821+}
822+
823+void ThrowingScope::stop()
824+{
825+}
826+
827+void ThrowingScope::run()
828+{
829+}
830+
831+SearchQueryBase::UPtr ThrowingScope::search(CannedQuery const& query, SearchMetadata const& metadata)
832+{
833+ if (query.query_string() == "throw from search")
834+ {
835+ throw ResourceException("exception from search");
836+ }
837+ lock_guard<mutex> lock(mutex_);
838+ return SearchQueryBase::UPtr(new TestQuery(query, metadata, id_));
839+}
840+
841+PreviewQueryBase::UPtr ThrowingScope::preview(Result const& result, ActionMetadata const& metadata)
842+{
843+ if (result.title() == "throw from preview")
844+ {
845+ throw ResourceException("exception from preview");
846+ }
847+ lock_guard<mutex> lock(mutex_);
848+ return PreviewQueryBase::UPtr(new TestPreview(result, metadata));
849+}
850+
851+ActivationQueryBase::UPtr ThrowingScope::activate(Result const& result, ActionMetadata const& metadata)
852+{
853+ if (result.title() == "throw from activate")
854+ {
855+ throw ResourceException("exception from activate");
856+ }
857+ lock_guard<mutex> lock(mutex_);
858+ return ActivationQueryBase::UPtr(new TestActivation(result, metadata));
859+}
860+
861+ActivationQueryBase::UPtr ThrowingScope::perform_action(Result const& result,
862+ ActionMetadata const& metadata,
863+ string const& /* widget_id */,
864+ string const& /* action_id */)
865+{
866+ if (result.title() == "throw from perform_action")
867+ {
868+ throw ResourceException("exception from perform_action");
869+ }
870+ lock_guard<mutex> lock(mutex_);
871+ return ActivationQueryBase::UPtr(new TestActivation(result, metadata));
872+}
873+
874+extern "C"
875+{
876+
877+ unity::scopes::ScopeBase*
878+ // cppcheck-suppress unusedFunction
879+ UNITY_SCOPE_CREATE_FUNCTION()
880+ {
881+ return new ThrowingScope;
882+ }
883+
884+ void
885+ // cppcheck-suppress unusedFunction
886+ UNITY_SCOPE_DESTROY_FUNCTION(unity::scopes::ScopeBase* scope_base)
887+ {
888+ delete scope_base;
889+ }
890+}
891
892=== added file 'test/gtest/scopes/ThrowingScope/ThrowingScope.h'
893--- test/gtest/scopes/ThrowingScope/ThrowingScope.h 1970-01-01 00:00:00 +0000
894+++ test/gtest/scopes/ThrowingScope/ThrowingScope.h 2015-02-04 05:47:34 +0000
895@@ -0,0 +1,49 @@
896+/*
897+ * Copyright (C) 2015 Canonical Ltd
898+ *
899+ * This program is free software: you can redistribute it and/or modify
900+ * it under the terms of the GNU Lesser General Public License version 3 as
901+ * published by the Free Software Foundation.
902+ *
903+ * This program is distributed in the hope that it will be useful,
904+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
905+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
906+ * GNU Lesser General Public License for more details.
907+ *
908+ * You should have received a copy of the GNU Lesser General Public License
909+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
910+ *
911+ * Authored by: Michi Henning <michi.henning@canonical.com>
912+ */
913+
914+#pragma once
915+
916+#include <unity/scopes/ScopeBase.h>
917+
918+class ThrowingScope : public unity::scopes::ScopeBase
919+{
920+public:
921+ virtual void start(std::string const&) override;
922+
923+ virtual void stop() override;
924+
925+ virtual void run() override;
926+
927+ virtual unity::scopes::SearchQueryBase::UPtr search(unity::scopes::CannedQuery const &,
928+ unity::scopes::SearchMetadata const &) override;
929+
930+ virtual unity::scopes::PreviewQueryBase::UPtr preview(unity::scopes::Result const&,
931+ unity::scopes::ActionMetadata const &) override;
932+
933+ virtual unity::scopes::ActivationQueryBase::UPtr activate(unity::scopes::Result const&,
934+ unity::scopes::ActionMetadata const &) override;
935+
936+ virtual unity::scopes::ActivationQueryBase::UPtr perform_action(unity::scopes::Result const&,
937+ unity::scopes::ActionMetadata const &,
938+ std::string const &,
939+ std::string const&) override;
940+
941+private:
942+ std::string id_;
943+ std::mutex mutex_;
944+};
945
946=== added file 'test/gtest/scopes/ThrowingScope/ThrowingScope.ini.in'
947--- test/gtest/scopes/ThrowingScope/ThrowingScope.ini.in 1970-01-01 00:00:00 +0000
948+++ test/gtest/scopes/ThrowingScope/ThrowingScope.ini.in 2015-02-04 05:47:34 +0000
949@@ -0,0 +1,4 @@
950+[ScopeConfig]
951+DisplayName = LoopScope
952+Description = Aggregator scope that makes various loops
953+Author = Michi
954
955=== added file 'test/gtest/scopes/ThrowingScope/ThrowingScope_test.cpp'
956--- test/gtest/scopes/ThrowingScope/ThrowingScope_test.cpp 1970-01-01 00:00:00 +0000
957+++ test/gtest/scopes/ThrowingScope/ThrowingScope_test.cpp 2015-02-04 05:47:34 +0000
958@@ -0,0 +1,477 @@
959+/*
960+ * Copyright (C) 2015 Canonical Ltd
961+ *
962+ * This program is free software: you can redistribute it and/or modify
963+ * it under the terms of the GNU Lesser General Public License version 3 as
964+ * published by the Free Software Foundation.
965+ *
966+ * This program is distributed in the hope that it will be useful,
967+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
968+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
969+ * GNU Lesser General Public License for more details.
970+ *
971+ * You should have received a copy of the GNU Lesser General Public License
972+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
973+ *
974+ * Authored by: Michi Henning <michi.henning@canonical.com>
975+ */
976+
977+#include <unity/scopes/CategorisedResult.h>
978+#include <unity/scopes/internal/RegistryObject.h>
979+#include <unity/scopes/internal/RuntimeImpl.h>
980+#include <unity/scopes/internal/ScopeImpl.h>
981+#include <unity/scopes/QueryCtrl.h>
982+
983+#include <gtest/gtest.h>
984+
985+#include "ThrowingScope.h"
986+
987+using namespace std;
988+using namespace unity::scopes;
989+using namespace unity::scopes::internal;
990+
991+class Receiver : public SearchListenerBase
992+{
993+public:
994+ Receiver()
995+ : query_complete_(false)
996+ {
997+ }
998+
999+ virtual void push(CategorisedResult result) override
1000+ {
1001+ lock_guard<mutex> lock(mutex_);
1002+
1003+ results_.push_back(move(result));
1004+ }
1005+
1006+ virtual void finished(CompletionDetails const& details) override
1007+ {
1008+ lock_guard<mutex> lock(mutex_);
1009+
1010+ completion_status_ = details.status();
1011+ completion_msg_ = details.message();
1012+ query_complete_ = true;
1013+ cond_.notify_one();
1014+ }
1015+
1016+ void wait_until_finished()
1017+ {
1018+ unique_lock<mutex> lock(mutex_);
1019+
1020+ cond_.wait(lock, [this] { return this->query_complete_; });
1021+ query_complete_ = false;
1022+ }
1023+
1024+ CompletionDetails::CompletionStatus completion_status()
1025+ {
1026+ lock_guard<mutex> lock(mutex_);
1027+
1028+ return completion_status_;
1029+ }
1030+
1031+ string completion_msg()
1032+ {
1033+ lock_guard<mutex> lock(mutex_);
1034+
1035+ return completion_msg_;
1036+ }
1037+
1038+ vector<CategorisedResult> results()
1039+ {
1040+ lock_guard<mutex> lock(mutex_);
1041+
1042+ return results_;
1043+ }
1044+
1045+private:
1046+ vector<CategorisedResult> results_;
1047+ bool query_complete_;
1048+ string completion_msg_;
1049+ CompletionDetails::CompletionStatus completion_status_;
1050+ mutex mutex_;
1051+ condition_variable cond_;
1052+};
1053+
1054+class PreviewListener : public PreviewListenerBase
1055+{
1056+public:
1057+ PreviewListener()
1058+ : preview_complete_(false)
1059+ {
1060+ }
1061+
1062+ void finished(const unity::scopes::CompletionDetails& details) override
1063+ {
1064+ lock_guard<mutex> lock(mutex_);
1065+
1066+ completion_status_ = details.status();
1067+ completion_msg_ = details.message();
1068+ preview_complete_ = true;
1069+ cond_.notify_one();
1070+ }
1071+
1072+ void wait_until_finished()
1073+ {
1074+ unique_lock<mutex> lock(mutex_);
1075+
1076+ cond_.wait(lock, [this] { return this->preview_complete_; });
1077+ preview_complete_ = false;
1078+ }
1079+
1080+ CompletionDetails::CompletionStatus completion_status()
1081+ {
1082+ lock_guard<mutex> lock(mutex_);
1083+
1084+ return completion_status_;
1085+ }
1086+
1087+ string completion_msg()
1088+ {
1089+ lock_guard<mutex> lock(mutex_);
1090+
1091+ return completion_msg_;
1092+ }
1093+
1094+ void push(ColumnLayoutList const&) override
1095+ {
1096+ }
1097+
1098+ void push(PreviewWidgetList const&) override
1099+ {
1100+ }
1101+
1102+ void push(std::string const&, Variant const&) override
1103+ {
1104+ }
1105+
1106+private:
1107+ bool preview_complete_;
1108+ string completion_msg_;
1109+ CompletionDetails::CompletionStatus completion_status_;
1110+ mutex mutex_;
1111+ condition_variable cond_;
1112+};
1113+
1114+class ActivationListener : public ActivationListenerBase
1115+{
1116+public:
1117+ ActivationListener()
1118+ : activate_complete_(false)
1119+ {
1120+ }
1121+
1122+ void activated(ActivationResponse const& response) override
1123+ {
1124+ lock_guard<mutex> lock(mutex_);
1125+
1126+ act_status_ = response.status();
1127+ }
1128+
1129+ void finished(const unity::scopes::CompletionDetails& details) override
1130+ {
1131+ lock_guard<mutex> lock(mutex_);
1132+
1133+ completion_status_ = details.status();
1134+ completion_msg_ = details.message();
1135+ activate_complete_ = true;
1136+ cond_.notify_one();
1137+ }
1138+
1139+ void wait_until_finished()
1140+ {
1141+ unique_lock<mutex> lock(mutex_);
1142+
1143+ cond_.wait(lock, [this] { return this->activate_complete_; });
1144+ activate_complete_ = false;
1145+ }
1146+
1147+ CompletionDetails::CompletionStatus completion_status()
1148+ {
1149+ lock_guard<mutex> lock(mutex_);
1150+
1151+ return completion_status_;
1152+ }
1153+
1154+ string completion_msg()
1155+ {
1156+ lock_guard<mutex> lock(mutex_);
1157+
1158+ return completion_msg_;
1159+ }
1160+
1161+private:
1162+ ActivationResponse::Status act_status_;
1163+ bool activate_complete_;
1164+ string completion_msg_;
1165+ CompletionDetails::CompletionStatus completion_status_;
1166+ mutex mutex_;
1167+ condition_variable cond_;
1168+};
1169+
1170+class ThrowingScopeTest : public ::testing::Test
1171+{
1172+public:
1173+ ThrowingScopeTest()
1174+ {
1175+ runtime_ = Runtime::create(TEST_RUNTIME_FILE);
1176+ auto reg = runtime_->registry();
1177+ auto meta = reg->get_metadata("ThrowingScope");
1178+ scope_ = meta.proxy();
1179+ }
1180+
1181+ ScopeProxy scope() const
1182+ {
1183+ return scope_;
1184+ }
1185+
1186+private:
1187+ Runtime::UPtr runtime_;
1188+ ScopeProxy scope_;
1189+};
1190+
1191+// The command file for each scope is called A.cmd, B.cmd, etc. It contains scope IDs, one per
1192+// line. For each scope ID in its command file, the scope sends a subsearch to that scope.
1193+// Once a scope's subsearch completes, it pushes a single result with the scope's ID as the
1194+// category ID. This allows us to set up various callgraphs by writing to the various command files.
1195+
1196+TEST_F(ThrowingScopeTest, no_error)
1197+{
1198+ auto receiver = make_shared<Receiver>();
1199+ scope()->search("success", SearchMetadata("unused", "unused"), receiver);
1200+ receiver->wait_until_finished();
1201+
1202+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1203+ EXPECT_EQ("", receiver->completion_msg());
1204+ auto r = receiver->results();
1205+ EXPECT_EQ(1, r.size());
1206+}
1207+
1208+TEST_F(ThrowingScopeTest, throw_from_search)
1209+{
1210+ auto receiver = make_shared<Receiver>();
1211+ scope()->search("throw from search", SearchMetadata("unused", "unused"), receiver);
1212+ receiver->wait_until_finished();
1213+
1214+ EXPECT_EQ(CompletionDetails::CompletionStatus::Error, receiver->completion_status());
1215+ EXPECT_EQ("unity::scopes::MiddlewareException: unity::ResourceException: Scope \"ThrowingScope\" "
1216+ "threw an exception from search():\n"
1217+ " unity::ResourceException: exception from search",
1218+ receiver->completion_msg());
1219+ auto r = receiver->results();
1220+ EXPECT_EQ(0, r.size());
1221+}
1222+
1223+TEST_F(ThrowingScopeTest, throw_from_run)
1224+{
1225+ auto receiver = make_shared<Receiver>();
1226+ scope()->search("throw from run", SearchMetadata("unused", "unused"), receiver);
1227+ receiver->wait_until_finished();
1228+
1229+ EXPECT_EQ(CompletionDetails::CompletionStatus::Error, receiver->completion_status());
1230+ EXPECT_EQ("QueryBase::run(): unity::ResourceException: exception from run", receiver->completion_msg());
1231+ auto r = receiver->results();
1232+ EXPECT_EQ(0, r.size());
1233+}
1234+
1235+TEST_F(ThrowingScopeTest, throw_from_cancelled)
1236+{
1237+ auto receiver = make_shared<Receiver>();
1238+ auto ctrl = scope()->search("throw from cancelled", SearchMetadata("unused", "unused"), receiver);
1239+ // Make sure that the cancel goes to the scope instead of being cached locally.
1240+ this_thread::sleep_for(chrono::seconds(1));
1241+ ctrl->cancel();
1242+ receiver->wait_until_finished();
1243+
1244+ auto r = receiver->results();
1245+ EXPECT_EQ(0, r.size());
1246+
1247+ EXPECT_EQ(CompletionDetails::CompletionStatus::Cancelled, receiver->completion_status());
1248+ // No exception string here because we short-cut the cancellation on the client side.
1249+ // The exception is logged though.
1250+ EXPECT_EQ("", receiver->completion_msg());
1251+}
1252+
1253+TEST_F(ThrowingScopeTest, throw_from_preview)
1254+{
1255+ auto receiver = make_shared<Receiver>();
1256+ scope()->search("throw from preview", SearchMetadata("unused", "unused"), receiver);
1257+ receiver->wait_until_finished();
1258+
1259+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1260+ EXPECT_EQ("", receiver->completion_msg());
1261+ auto r = receiver->results();
1262+ EXPECT_EQ(1, r.size());
1263+
1264+ auto plistener = make_shared<PreviewListener>();
1265+ scope()->preview(r[0], ActionMetadata("unused", "unused"), plistener);
1266+ plistener->wait_until_finished();
1267+
1268+ EXPECT_EQ(CompletionDetails::CompletionStatus::Error, plistener->completion_status());
1269+ EXPECT_EQ("unity::scopes::MiddlewareException: unity::ResourceException: Scope \"ThrowingScope\" "
1270+ "threw an exception from preview():\n"
1271+ " unity::ResourceException: exception from preview",
1272+ plistener->completion_msg());
1273+}
1274+
1275+TEST_F(ThrowingScopeTest, throw_from_preview_run)
1276+{
1277+ auto receiver = make_shared<Receiver>();
1278+ scope()->search("throw from preview run", SearchMetadata("unused", "unused"), receiver);
1279+ receiver->wait_until_finished();
1280+
1281+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1282+ EXPECT_EQ("", receiver->completion_msg());
1283+ auto r = receiver->results();
1284+ EXPECT_EQ(1, r.size());
1285+
1286+ auto plistener = make_shared<PreviewListener>();
1287+ scope()->preview(r[0], ActionMetadata("unused", "unused"), plistener);
1288+ plistener->wait_until_finished();
1289+
1290+ EXPECT_EQ(CompletionDetails::CompletionStatus::Error, plistener->completion_status());
1291+ EXPECT_EQ("PreviewQueryBase::run(): unity::ResourceException: throw from preview run", plistener->completion_msg());
1292+}
1293+
1294+TEST_F(ThrowingScopeTest, throw_from_preview_cancelled)
1295+{
1296+ auto receiver = make_shared<Receiver>();
1297+ scope()->search("throw from preview cancelled", SearchMetadata("unused", "unused"), receiver);
1298+ receiver->wait_until_finished();
1299+
1300+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1301+ EXPECT_EQ("", receiver->completion_msg());
1302+ auto r = receiver->results();
1303+ EXPECT_EQ(1, r.size());
1304+
1305+ auto plistener = make_shared<PreviewListener>();
1306+ auto ctrl = scope()->preview(r[0], ActionMetadata("unused", "unused"), plistener);
1307+ this_thread::sleep_for(chrono::seconds(1));
1308+ ctrl->cancel();
1309+ plistener->wait_until_finished();
1310+
1311+ EXPECT_EQ(CompletionDetails::CompletionStatus::Cancelled, plistener->completion_status());
1312+ // No exception string here because we short-cut the cancellation on the client side.
1313+ // The exception is logged though.
1314+ EXPECT_EQ("", plistener->completion_msg());
1315+}
1316+
1317+TEST_F(ThrowingScopeTest, throw_from_activate)
1318+{
1319+ auto receiver = make_shared<Receiver>();
1320+ scope()->search("throw from activate", SearchMetadata("unused", "unused"), receiver);
1321+ receiver->wait_until_finished();
1322+
1323+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1324+ EXPECT_EQ("", receiver->completion_msg());
1325+ auto r = receiver->results();
1326+ EXPECT_EQ(1, r.size());
1327+
1328+ auto alistener = make_shared<ActivationListener>();
1329+ scope()->activate(r[0], ActionMetadata("unused", "unused"), alistener);
1330+ alistener->wait_until_finished();
1331+
1332+ EXPECT_EQ(CompletionDetails::CompletionStatus::Error, alistener->completion_status());
1333+ EXPECT_EQ("unity::scopes::MiddlewareException: unity::ResourceException: Scope \"ThrowingScope\" "
1334+ "threw an exception from activate():\n"
1335+ " unity::ResourceException: exception from activate",
1336+ alistener->completion_msg());
1337+}
1338+
1339+TEST_F(ThrowingScopeTest, throw_from_activation_activate)
1340+{
1341+ auto receiver = make_shared<Receiver>();
1342+ scope()->search("throw from activation activate", SearchMetadata("unused", "unused"), receiver);
1343+ receiver->wait_until_finished();
1344+
1345+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1346+ EXPECT_EQ("", receiver->completion_msg());
1347+ auto r = receiver->results();
1348+ EXPECT_EQ(1, r.size());
1349+
1350+ auto alistener = make_shared<ActivationListener>();
1351+ scope()->activate(r[0], ActionMetadata("unused", "unused"), alistener);
1352+ alistener->wait_until_finished();
1353+
1354+ EXPECT_EQ(CompletionDetails::CompletionStatus::Error, alistener->completion_status());
1355+ EXPECT_EQ("ActivationQueryBase::activate(): unity::ResourceException: throw from activation activate",
1356+ alistener->completion_msg());
1357+}
1358+
1359+TEST_F(ThrowingScopeTest, throw_from_activation_cancelled)
1360+{
1361+ auto receiver = make_shared<Receiver>();
1362+ scope()->search("throw from activation cancelled", SearchMetadata("unused", "unused"), receiver);
1363+ receiver->wait_until_finished();
1364+
1365+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1366+ EXPECT_EQ("", receiver->completion_msg());
1367+ auto r = receiver->results();
1368+ EXPECT_EQ(1, r.size());
1369+
1370+ auto alistener = make_shared<ActivationListener>();
1371+ auto ctrl = scope()->activate(r[0], ActionMetadata("unused", "unused"), alistener);
1372+ this_thread::sleep_for(chrono::seconds(1));
1373+ ctrl->cancel();
1374+ alistener->wait_until_finished();
1375+
1376+ EXPECT_EQ(CompletionDetails::CompletionStatus::Cancelled, alistener->completion_status());
1377+ // No exception string here because we short-cut the cancellation on the client side.
1378+ // The exception is logged though.
1379+ EXPECT_EQ("", alistener->completion_msg());
1380+}
1381+
1382+TEST_F(ThrowingScopeTest, throw_from_perform_action)
1383+{
1384+ auto receiver = make_shared<Receiver>();
1385+ scope()->search("throw from perform_action", SearchMetadata("unused", "unused"), receiver);
1386+ receiver->wait_until_finished();
1387+
1388+ EXPECT_EQ(CompletionDetails::CompletionStatus::OK, receiver->completion_status());
1389+ EXPECT_EQ("", receiver->completion_msg());
1390+ auto r = receiver->results();
1391+ EXPECT_EQ(1, r.size());
1392+
1393+ auto alistener = make_shared<ActivationListener>();
1394+ scope()->perform_action(r[0], ActionMetadata("unused", "unused"), "", "", alistener);
1395+ alistener->wait_until_finished();
1396+
1397+ EXPECT_EQ(CompletionDetails::CompletionStatus::Error, alistener->completion_status());
1398+ EXPECT_EQ("unity::scopes::MiddlewareException: unity::ResourceException: Scope \"ThrowingScope\" "
1399+ "threw an exception from perform_action():\n"
1400+ " unity::ResourceException: exception from perform_action",
1401+ alistener->completion_msg());
1402+}
1403+
1404+int main(int argc, char **argv)
1405+{
1406+ ::testing::InitGoogleTest(&argc, argv);
1407+
1408+ int rc = 0;
1409+
1410+ // Set the "TEST_DESKTOP_FILES_DIR" env var before forking as not to create desktop files in ~/.local
1411+ putenv(const_cast<char*>("TEST_DESKTOP_FILES_DIR=" TEST_RUNTIME_PATH));
1412+
1413+ auto rpid = fork();
1414+ if (rpid == 0)
1415+ {
1416+ const char* const args[] = {"scoperegistry [ThrowingScope_test]", TEST_RUNTIME_FILE, nullptr};
1417+ if (execv(TEST_REGISTRY_PATH "/scoperegistry", const_cast<char* const*>(args)) < 0)
1418+ {
1419+ perror("Error starting scoperegistry:");
1420+ }
1421+ return 1;
1422+ }
1423+ else if (rpid > 0)
1424+ {
1425+ rc = RUN_ALL_TESTS();
1426+ kill(rpid, SIGTERM);
1427+ waitpid(rpid, nullptr, 0);
1428+ }
1429+ else
1430+ {
1431+ perror("Failed to fork:");
1432+ }
1433+
1434+ return rc;
1435+}
1436
1437=== added file 'test/gtest/scopes/ThrowingScope/Zmq.ini.in'
1438--- test/gtest/scopes/ThrowingScope/Zmq.ini.in 1970-01-01 00:00:00 +0000
1439+++ test/gtest/scopes/ThrowingScope/Zmq.ini.in 2015-02-04 05:47:34 +0000
1440@@ -0,0 +1,2 @@
1441+[Zmq]
1442+EndpointDir = /tmp

Subscribers

People subscribed via source and target branches

to all changes: