Merge lp:~michihenning/unity-scopes-api/cache-results into lp:unity-scopes-api

Proposed by Michi Henning
Status: Merged
Approved by: Paweł Stołowski
Approved revision: 582
Merged at revision: 312
Proposed branch: lp:~michihenning/unity-scopes-api/cache-results
Merge into: lp:unity-scopes-api
Prerequisite: lp:~michihenning/unity-scopes-api/add-logging
Diff against target: 1650 lines (+1136/-36)
36 files modified
RELEASE_NOTES.md (+9/-0)
debian/changelog (+14/-0)
debian/libunity-scopes3.symbols (+2/-0)
doc/tutorial.dox (+13/-0)
include/unity/scopes/CategorisedResult.h (+2/-0)
include/unity/scopes/Category.h (+2/-0)
include/unity/scopes/SearchReply.h (+21/-0)
include/unity/scopes/internal/' (+59/-0)
include/unity/scopes/internal/CategoryRegistry.h (+8/-1)
include/unity/scopes/internal/RegistryImpl.h (+0/-2)
include/unity/scopes/internal/RuntimeImpl.h (+3/-1)
include/unity/scopes/internal/SearchReplyImpl.h (+14/-0)
include/unity/scopes/testing/MockSearchReply.h (+1/-0)
include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h (+6/-3)
src/scopes/internal/CategoryRegistry.cpp (+19/-6)
src/scopes/internal/QueryObject.cpp (+1/-0)
src/scopes/internal/RuntimeImpl.cpp (+10/-0)
src/scopes/internal/SearchReplyImpl.cpp (+259/-14)
src/scopes/internal/smartscopes/SSQueryObject.cpp (+1/-0)
src/scopes/testing/InProcessBenchmark.cpp (+4/-0)
src/scopes/utility/internal/BufferedSearchReplyImpl.cpp (+5/-0)
test/gtest/scopes/CMakeLists.txt (+1/-0)
test/gtest/scopes/ResultCache/CMakeLists.txt (+27/-0)
test/gtest/scopes/ResultCache/CacheScope.cpp (+160/-0)
test/gtest/scopes/ResultCache/CacheScope.h (+41/-0)
test/gtest/scopes/ResultCache/CacheScope.ini.in (+4/-0)
test/gtest/scopes/ResultCache/ResultCache_test.cpp (+414/-0)
test/gtest/scopes/ResultCache/Runtime.ini.in (+8/-0)
test/gtest/scopes/ResultCache/TestRegistry.ini.in (+7/-0)
test/gtest/scopes/ResultCache/Zmq.ini.in (+2/-0)
test/gtest/scopes/ResultCache/no_cat_cache (+1/-0)
test/gtest/scopes/ResultCache/no_dept_cache (+1/-0)
test/gtest/scopes/ResultCache/no_filter_state_cache (+1/-0)
test/gtest/scopes/ResultCache/no_filters_cache (+1/-0)
test/gtest/scopes/ResultCache/no_results_cache (+1/-0)
test/gtest/scopes/internal/RuntimeConfig/RuntimeConfig_test.cpp (+14/-9)
To merge this branch: bzr merge lp:~michihenning/unity-scopes-api/cache-results
Reviewer Review Type Date Requested Status
Paweł Stołowski (community) Needs Fixing
Michi Henning (community) Disapprove
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+246257@code.launchpad.net

This proposal supersedes a proposal from 2015-01-13.

Commit message

Added result cache that stores the results of the last successful surfacing query. The scope, if it detects connectivity problems, can call reply->push_surfacing_results_from_cache(). This answers the query from the cache, provided that the query is indeed a surfacing query. Otherwise, the method does nothing.

Note: This is change is ABI compatible with gcc and clang despite the addition of a new virtual function.

Description of the change

Added result cache that stores the results of the last successful surfacing query. The scope, if it detects connectivity problems, can call reply->push_surfacing_results_from_cache(). This answers the query from the cache, provided that the query is indeed a surfacing query. Otherwise, the method does nothing.

Note: This is change is ABI compatible with gcc and clang despite the addition of a new virtual function.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:572
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~michihenning/unity-scopes-api/cache-results/+merge/246257/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-scopes-api-ci/505/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-scopes-api-vivid-amd64-ci/30/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-scopes-api-vivid-armhf-ci/30/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-scopes-api-vivid-i386-ci/30/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/unity-scopes-api-ci/505/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

Re-submitting a proposal causes launchpad to nuke the commit message. Neat…

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

Marking this as work in progress until we get the go-ahead for an ABI-breaking change.

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

I've checked that this doesn't break anything when we substitute the new library underneath code that was compiled against the previous version, so I'm putting this back up for review.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
576. By Michi Henning

Merged trunk and resolved conflicts.

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
Paweł Stołowski (stolowski) wrote :

279 + std::vector<CatPair> categories_; // vector instead of map, so we preserve order

Hmm, we do call lookup_category *a lot* internally (practically for every result), so I'm a little bit worried about the additional cpu cycles with linear search, especially in aggregation scenarios. Two cases where we call it:

1) CategorisedResultImpl::CategorisedResultImpl(internal::CategoryRegistry const& reg, VariantMap const& variant_map)

and:

2) bool SearchReplyImpl::push(unity::scopes::CategorisedResult const& result)
{
    // If this is an aggregator scope, it may be pushing result items obtained
    // from ReplyObject without registering a category first.
    if (cat_registry_->lookup_category(result.category()->id()) == nullptr)
    {
        register_category(result.category());

(I don't remember from top of my head why we do it in 1st case, but the 2nd one has a good comment explaining it).

Is it really important to preserve the order of categories, given that the order of results determines actual order? If so, perhaps it would make sense to keep a map aside for faster lookups?

review: Needs Information
Revision history for this message
Paweł Stołowski (stolowski) wrote :

> 1) CategorisedResultImpl::CategorisedResultImpl(internal::CategoryRegistry
> const& reg, VariantMap const& variant_map)
>
> and:
>
> 2) bool SearchReplyImpl::push(unity::scopes::CategorisedResult const& result)
> {
> // If this is an aggregator scope, it may be pushing result items obtained
> // from ReplyObject without registering a category first.
> if (cat_registry_->lookup_category(result.category()->id()) == nullptr)
> {
> register_category(result.category());
>
> (I don't remember from top of my head why we do it in 1st case, but the 2nd
> one has a good comment explaining it).

Ah, now I remember, the 1st case is when we deserialize incoming results and need category pointers.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

824 + VariantMap dict = r.get_dict();
825 + string const cat_id = dict.at("internal").get_dict().at("cat_id").get_string();
826 + auto cat = lookup_category(cat_id);
827 + auto crip = new CategorisedResultImpl(move(cat), move(dict));
828 + auto cr = CategorisedResult(crip);

I think this could be simplified by constructing CategorisedResult using this ctor:
CategorisedResultImpl::CategorisedResultImpl(internal::CategoryRegistry const& reg, VariantMap const& variant_map)

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Hmm, I think this will need to be extended to support filters (we only realistically need to support option selector filter atm, but if you use FilterBaseImpl::deserialize_filters / serialize_filters, it will work for all of them with no extra work).

Other than the above this looks really cool, I like it a lot!

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

> Hmm, we do call lookup_category *a lot* internally (practically for every
> result), so I'm a little bit worried about the additional cpu cycles with
> linear search, especially in aggregation scenarios. Two cases where we call
> it:
>
> 1) CategorisedResultImpl::CategorisedResultImpl(internal::CategoryRegistry
> const& reg, VariantMap const& variant_map)
>
> and:
>
> 2) bool SearchReplyImpl::push(unity::scopes::CategorisedResult const& result)
> {
> // If this is an aggregator scope, it may be pushing result items obtained
> // from ReplyObject without registering a category first.
> if (cat_registry_->lookup_category(result.category()->id()) == nullptr)
> {
> register_category(result.category());
>
> (I don't remember from top of my head why we do it in 1st case, but the 2nd
> one has a good comment explaining it).
>
> Is it really important to preserve the order of categories, given that the
> order of results determines actual order? If so, perhaps it would make sense
> to keep a map aside for faster lookups?

Well... Even assuming that linear search will be slower, it doesn't matter. We are going on the wire for each of these calls. The cost of finding the category is minuscule compared to pushing a result.

But, secondly, linear search will almost certainly *not* be slower. A vector will be faster than a map unless there are at least several thousand elements. That's because the elements of a vector are contiguous in memory, so vector plays very nicely with the CPU caches. In contrast, pretty much every node in a map will be at a completely different memory location, meaning that on every step of the lookup, we get a cache miss and do a trip to main memory (which is around 1000 times slower than a cache hit).

The old adage about O(log n) being preferable is true only if n is large and if we assume that all data accesses have equal cost. With a cache miss adding a constant factor of around 1000, O(n) with a constant factor of 1 is miles faster because our n is small.

But, anyway, for our performance requirements, it truly doesn't matter. Either would be plenty fast enough :-) I used a vector simply because I needed to preserve order, not because of performance concerns.

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

> I think this could be simplified by constructing CategorisedResult using this
> ctor:
> CategorisedResultImpl::CategorisedResultImpl(internal::CategoryRegistry const&
> reg, VariantMap const& variant_map)

Fixed, thanks for that!

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

> Hmm, I think this will need to be extended to support filters (we only
> realistically need to support option selector filter atm, but if you use
> FilterBaseImpl::deserialize_filters / serialize_filters, it will work for all
> of them with no extra work).

Caching applies only to surfacing queries. I thought that these can never have a filter?

577. By Michi Henning

Merged trunk.

578. By Michi Henning

Simplified CategorisedResult instantiation when pushing from cache.

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 :

> But, anyway, for our performance requirements, it truly doesn't matter. Either
> would be plenty fast enough :-) I used a vector simply because I needed to
> preserve order, not because of performance concerns.

Fair enough, I think you're right :)

Revision history for this message
Paweł Stołowski (stolowski) wrote :

> > Hmm, I think this will need to be extended to support filters (we only
> > realistically need to support option selector filter atm, but if you use
> > FilterBaseImpl::deserialize_filters / serialize_filters, it will work for
> all
> > of them with no extra work).
>
> Caching applies only to surfacing queries. I thought that these can never have
> a filter?

I don't think we should be making this assumption. Nothing stops scope author from pushing departments and an additional navigation menu (single-selection filter) atm.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
579. By Michi Henning

Added filters and filter_state to cached results and added corresponding tests.

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 :

Great, thank you!

review: Approve
Revision history for this message
Paweł Stołowski (stolowski) wrote :

> Note: This is change is ABI compatible with gcc and clang despite the addition of a new virtual function.

I'll never ever believe you when you say that ;) [details in the email]

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

After discussing with Thomas, I'm rejecting this MR for the time being. This is just too bloody dangerous. It's better not to have the feature than having the uncertainty of random scopes getting core dumps.

review: Disapprove
580. By Michi Henning

Moved push_surfacing_results_from_cache to end of virtual functions to
avoid ABI break.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Michi, could you please merge trunk?

review: Needs Fixing
581. By Michi Henning

Merged trunk and resolved conflicts.

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

> Michi, could you please merge trunk?

Done.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Michi, you need to bump micro version again (we are already at 0.6.13) and update changelog and release notes.

582. By Michi Henning

Update micro version, changelog, and release notes.

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

Thanks for the heads-up, and my apologies for that! This branch is getting kind of long in the tooth, seeing that I first pushed it a month ago. Oh, the world is marching on around me ;-)

583. By Michi Henning

Fixed changelog. (Thanks Pawel!)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'RELEASE_NOTES.md'
2--- RELEASE_NOTES.md 2015-01-28 09:06:08 +0000
3+++ RELEASE_NOTES.md 2015-02-05 09:36:37 +0000
4@@ -16,6 +16,15 @@
5
6 Changes in version 0.6.11
7 =========================
8+ - Added push_surfacing_results_from_cache() to Reply proxy. This allows a scope
9+ to reply the results of the last succesful surfacing query from an on-disk cache.
10+ This is useful to prevent the user being presented with an empty screen when
11+ swiping to the scope while the device has no network access, or the scope's
12+ data source is off-line.
13+
14+ Note: This is change is ABI compatible with gcc and clang despite the addition
15+ a new virtual function.
16+
17 - The JSON for a CategoryRenderer now supports a "fallback" field in the
18 "art" and "mascot" entries of the "components" dictionary.
19 This allows a scope to specify a category-specific
20
21=== modified file 'debian/changelog'
22--- debian/changelog 2015-01-30 12:34:42 +0000
23+++ debian/changelog 2015-02-05 09:36:37 +0000
24@@ -95,6 +95,20 @@
25
26 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 19 Jan 2015 08:09:11 +0000
27
28+unity-scopes-api (0.6.11+15.04) UNRELEASED; urgency=medium
29+
30+ [ Michi Henning ]
31+ * Added push_surfacing_results_from_cache() to Reply proxy. This allows a scope
32+ to reply the results of the last succesful surfacing query from an on-disk cache.
33+ This is useful to prevent the user being presented with an empty screen when
34+ swiping to the scope while the device has no network access, or the scope's
35+ data source is off-line.
36+
37+ Note: This is change is ABI compatible with gcc and clang despite the addition
38+ a new virtual function.
39+
40+ -- Michi Henning <michi.henning@canonical.com> Thu, 15 Jan 2015 14:22:17 +1000
41+
42 unity-scopes-api (0.6.11-0ubuntu1) UNRELEASED; urgency=medium
43
44 * Improved logging internals.
45
46=== modified file 'debian/libunity-scopes3.symbols'
47--- debian/libunity-scopes3.symbols 2015-01-30 12:34:39 +0000
48+++ debian/libunity-scopes3.symbols 2015-02-05 09:36:37 +0000
49@@ -423,6 +423,7 @@
50 (c++)"unity::scopes::internal::RuntimeConfig::ss_registry_identity() const@Base" 0.4.4+14.10.20140508
51 (c++)"unity::scopes::internal::RuntimeConfig::trace_channels() const@Base" 0.6.11+15.04.20150119
52 (c++)"unity::scopes::internal::RuntimeImpl::async_pool() const@Base" 0.4.3+14.10.20140428
53+ (c++)"unity::scopes::internal::RuntimeImpl::cache_directory() const@Base" 0replaceme
54 (c++)"unity::scopes::internal::RuntimeImpl::confined() const@Base" 0.6.8+15.04.20141119
55 (c++)"unity::scopes::internal::RuntimeImpl::confinement_type() const@Base" 0.6.8+15.04.20141119
56 (c++)"unity::scopes::internal::RuntimeImpl::create(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.4.0+14.04.20140312.1
57@@ -448,6 +449,7 @@
58 (c++)"unity::scopes::internal::RuntimeImpl::ss_configfile() const@Base" 0.4.4+14.10.20140508
59 (c++)"unity::scopes::internal::RuntimeImpl::ss_registry_identity() const@Base" 0.4.4+14.10.20140508
60 (c++)"unity::scopes::internal::RuntimeImpl::string_to_proxy(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const@Base" 0.4.0+14.04.20140312.1
61+ (c++)"unity::scopes::internal::RuntimeImpl::tmp_directory() const@Base" 0replaceme
62 (c++)"unity::scopes::internal::RuntimeImpl::waiter_thread(std::shared_ptr<unity::scopes::internal::ThreadSafeQueue<std::future<void> > > const&) const@Base" 0.4.3+14.10.20140428
63 (c++)"unity::scopes::internal::safe_strerror(int)@Base" 0.6.9+15.04.20141129
64 (c++)"unity::scopes::internal::ScopeConfig::appearance_attributes() const@Base" 0.4.2+14.04.20140404.2
65
66=== modified file 'doc/tutorial.dox'
67--- doc/tutorial.dox 2015-01-06 06:08:09 +0000
68+++ doc/tutorial.dox 2015-02-05 09:36:37 +0000
69@@ -229,6 +229,19 @@
70 here (if at all possible), so the user gets to see at least some results (instead of being confronted with
71 a blank screen).
72
73+The runtime automatically saves the results of the most recent surfacing query. If a scope cannot produce
74+a result for a surfacing query (presumably, due to connectivity problems), calling
75+\link unity::scopes::SearchReply::push_surfacing_results_from_cache() push_surfacing_results_from_cache()\endlink
76+pushes the results that were produced by the most recent successful
77+surfacing query from the cache. If your scope cannot produce surfacing results, you can call this
78+method to "replay" the results of the previous surfacing query. In turn, this avoids the user
79+being presented with an empty screen if he/she swipes to the scope while the device does not have connectivity.
80+
81+`push_surfacing_results_from_cache()` has an effect only if called for a surfacing query (that is, a query with an empty query string). If called for a non-empty query, it does nothing.
82+
83+You must call this method before calling \link unity::scopes::Reply::finished finished()\endlink,
84+otherwise no cached results will be pushed. (`push_surfacing_results_from_cache() implicitly calls `finished()`);
85+
86 \paragraph querybase Implementing QueryBase
87
88 You must implement a class that derives from \link unity::scopes::SearchQueryBase SearchQueryBase\endlink and return
89
90=== modified file 'include/unity/scopes/CategorisedResult.h'
91--- include/unity/scopes/CategorisedResult.h 2014-11-03 05:31:30 +0000
92+++ include/unity/scopes/CategorisedResult.h 2015-02-05 09:36:37 +0000
93@@ -31,6 +31,7 @@
94 {
95 class CategorisedResultImpl;
96 class ResultReplyObject;
97+ class SearchReplyImpl;
98 }
99
100 /**
101@@ -80,6 +81,7 @@
102
103 friend class internal::CategorisedResultImpl;
104 friend class internal::ResultReplyObject;
105+ friend class internal::SearchReplyImpl;
106 };
107
108 } // namespace scopes
109
110=== modified file 'include/unity/scopes/Category.h'
111--- include/unity/scopes/Category.h 2015-01-26 08:20:45 +0000
112+++ include/unity/scopes/Category.h 2015-02-05 09:36:37 +0000
113@@ -36,6 +36,7 @@
114 {
115 class CategoryImpl;
116 class CategoryRegistry;
117+ class SearchReplyImpl;
118 }
119
120 /**
121@@ -100,6 +101,7 @@
122 /// @endcond
123
124 friend class internal::CategoryRegistry;
125+ friend class internal::SearchReplyImpl;
126
127 private:
128 std::unique_ptr<internal::CategoryImpl> p;
129
130=== modified file 'include/unity/scopes/SearchReply.h'
131--- include/unity/scopes/SearchReply.h 2014-11-03 05:31:30 +0000
132+++ include/unity/scopes/SearchReply.h 2015-02-05 09:36:37 +0000
133@@ -141,6 +141,27 @@
134 virtual bool push(Filters const& filters, FilterState const& filter_state) = 0;
135
136 /**
137+ \brief Push the results that were produced by the most recent surfacing query.
138+
139+ The runtime automatically saves the results of the most recent surfacing query.
140+ If a scope cannot produce a result for a surfacing query (presumably, due to
141+ connectivity problems), calling push_surfacing_results_from_cache() pushes
142+ the results that were produced by the most recent successful surfacing query
143+ from the cache. If a scope cannot produce surfacing results, it can call this
144+ method to "replay" the previous results. In turn, this avoids the user being
145+ presented with an empty screen if he/she swipes to the scope while the device
146+ does not have connectivity.
147+
148+ This method has an effect only if called for a surfacing query (that is, a
149+ query with an empty query string). If called for a non-empty query, it does
150+ nothing.
151+
152+ You must call this method before calling finished(), otherwise no cached results
153+ will be pushed (push_surfacing_results_from_cache() implicitly calls finished()).
154+ */
155+ virtual void push_surfacing_results_from_cache() = 0;
156+
157+ /**
158 \brief Destroys a Reply.
159
160 If a Reply goes out of scope without a prior call to finished(), the destructor implicitly calls finished().
161
162=== added file 'include/unity/scopes/internal/''
163--- include/unity/scopes/internal/' 1970-01-01 00:00:00 +0000
164+++ include/unity/scopes/internal/' 2015-02-05 09:36:37 +0000
165@@ -0,0 +1,59 @@
166+/*
167+ * Copyright (C) 2013 Canonical Ltd
168+ *
169+ * This program is free software: you can redistribute it and/or modify
170+ * it under the terms of the GNU Lesser General Public License version 3 as
171+ * published by the Free Software Foundation.
172+ *
173+ * This program is distributed in the hope that it will be useful,
174+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
175+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
176+ * GNU Lesser General Public License for more details.
177+ *
178+ * You should have received a copy of the GNU Lesser General Public License
179+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
180+ *
181+ * Authored by: Michi Henning <michi.henning@canonical.com>
182+ */
183+
184+#pragma once
185+
186+#include <unity/scopes/internal/MWRegistryProxyFwd.h>
187+#include <unity/scopes/internal/ObjectImpl.h>
188+#include <unity/scopes/Registry.h>
189+
190+namespace unity
191+{
192+
193+namespace scopes
194+{
195+
196+namespace internal
197+{
198+
199+class RegistryImpl : public virtual unity::scopes::Registry, public virtual ObjectImpl
200+{
201+public:
202+ RegistryImpl(MWRegistryProxy const& mw_proxy);
203+ ~RegistryImpl();
204+
205+ virtual ScopeMetadata get_metadata(std::string const& scope_id) override;
206+ virtual MetadataMap list() override;
207+ virtual MetadataMap list_if(std::function<bool(ScopeMetadata const& item)> predicate) override;
208+ virtual bool is_scope_running(std::string const& scope_id) override;
209+
210+ virtual core::ScopedConnection set_scope_state_callback(std::string const& scope_id, std::function<void(bool)> callback) override;
211+ virtual core::ScopedConnection set_list_update_callback(std::function<void()> callback) override;
212+
213+ // Remote operation. Not part of public API, hence not override.
214+ ObjectProxy locate(std::string const& identity);
215+
216+private:
217+ MWRegistryProxy fwd();
218+};
219+
220+} // namespace internal
221+
222+} // namespace scopes
223+
224+} // namespace unity
225
226=== modified file 'include/unity/scopes/internal/CategoryRegistry.h'
227--- include/unity/scopes/internal/CategoryRegistry.h 2015-01-14 02:19:17 +0000
228+++ include/unity/scopes/internal/CategoryRegistry.h 2015-02-05 09:36:37 +0000
229@@ -71,9 +71,16 @@
230 */
231 void register_category(Category::SCPtr category);
232
233+ /**
234+ \brief Serializes all categories in the registry.
235+ \return VariantArray containing all categories.
236+ */
237+ VariantArray serialize() const;
238+
239 private:
240 mutable std::mutex mutex_;
241- std::map<std::string, Category::SCPtr> categories_;
242+ typedef std::pair<std::string, Category::SCPtr> CatPair;
243+ std::vector<CatPair> categories_; // vector instead of map, so we preserve order
244 };
245
246 } // namespace internal
247
248=== modified file 'include/unity/scopes/internal/RegistryImpl.h'
249--- include/unity/scopes/internal/RegistryImpl.h 2015-01-09 03:16:51 +0000
250+++ include/unity/scopes/internal/RegistryImpl.h 2015-02-05 09:36:37 +0000
251@@ -31,8 +31,6 @@
252 namespace internal
253 {
254
255-class RuntimeImpl;
256-
257 class RegistryImpl : public virtual unity::scopes::Registry, public virtual ObjectImpl
258 {
259 public:
260
261=== modified file 'include/unity/scopes/internal/RuntimeImpl.h'
262--- include/unity/scopes/internal/RuntimeImpl.h 2015-01-16 02:59:43 +0000
263+++ include/unity/scopes/internal/RuntimeImpl.h 2015-02-05 09:36:37 +0000
264@@ -62,6 +62,9 @@
265 ObjectProxy string_to_proxy(std::string const& s) const;
266 std::string proxy_to_string(ObjectProxy const& proxy) const;
267
268+ std::string cache_directory() const;
269+ std::string tmp_directory() const;
270+
271 ~RuntimeImpl();
272
273 private:
274@@ -91,7 +94,6 @@
275 std::string app_dir_;
276 std::string log_dir_;
277 std::string config_dir_;
278- std::string tmp_dir_;
279 Logger::UPtr logger_;
280 mutable Reaper::SPtr reply_reaper_;
281 mutable ThreadPool::SPtr async_pool_; // Pool of invocation threads for async query creation
282
283=== modified file 'include/unity/scopes/internal/SearchReplyImpl.h'
284--- include/unity/scopes/internal/SearchReplyImpl.h 2015-01-09 03:16:51 +0000
285+++ include/unity/scopes/internal/SearchReplyImpl.h 2015-02-05 09:36:37 +0000
286@@ -53,6 +53,7 @@
287 SearchReplyImpl(MWReplyProxy const& mw_proxy,
288 std::shared_ptr<QueryObjectBase>const & qo,
289 int cardinality,
290+ std::string const& query_string,
291 std::string const& current_department_id);
292 virtual ~SearchReplyImpl();
293
294@@ -75,14 +76,27 @@
295 virtual bool push(unity::scopes::CategorisedResult const& result) override;
296 virtual bool push(unity::scopes::Filters const& filters, unity::scopes::FilterState const& filter_state) override;
297
298+ virtual void finished() override;
299+
300+ virtual void push_surfacing_results_from_cache() noexcept;
301+
302 private:
303 bool push(Category::SCPtr category);
304+ void write_cached_results() noexcept;
305
306 std::shared_ptr<CategoryRegistry> cat_registry_;
307
308 std::atomic_int cardinality_;
309 std::atomic_int num_pushes_;
310+ std::atomic_bool finished_;
311+ std::string query_string_;
312 std::string current_department_;
313+
314+ Department::SCPtr cached_departments_;
315+ unity::scopes::Filters cached_filters_;
316+ unity::scopes::FilterState cached_filter_state_;
317+ std::vector<unity::scopes::CategorisedResult> cached_results_;
318+ std::mutex mutex_;
319 };
320
321 } // namespace internal
322
323=== modified file 'include/unity/scopes/testing/MockSearchReply.h'
324--- include/unity/scopes/testing/MockSearchReply.h 2014-11-03 05:31:30 +0000
325+++ include/unity/scopes/testing/MockSearchReply.h 2015-02-05 09:36:37 +0000
326@@ -65,6 +65,7 @@
327 MOCK_METHOD1(push, bool(CategorisedResult const&));
328 MOCK_METHOD2(push, bool(Filters const&, FilterState const&));
329 MOCK_METHOD1(push, bool(experimental::Annotation const& annotation));
330+ MOCK_METHOD0(push_surfacing_results_from_cache, void());
331 };
332
333 /// @endcond
334
335=== modified file 'include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h'
336--- include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h 2014-12-17 01:48:23 +0000
337+++ include/unity/scopes/utility/internal/BufferedSearchReplyImpl.h 2015-02-05 09:36:37 +0000
338@@ -18,13 +18,14 @@
339
340 #pragma once
341
342+#include <unity/scopes/CategorisedResult.h>
343 #include <unity/scopes/SearchReply.h>
344 #include <unity/scopes/SearchReplyProxyFwd.h>
345-#include <unity/scopes/CategorisedResult.h>
346+
347+#include <atomic>
348 #include <memory>
349+#include <mutex>
350 #include <vector>
351-#include <mutex>
352-#include <atomic>
353
354 namespace unity
355 {
356@@ -62,6 +63,8 @@
357 bool push(unity::scopes::CategorisedResult const& result) override;
358 bool push(unity::scopes::Filters const& filters, unity::scopes::FilterState const& filter_state) override;
359
360+ void push_surfacing_results_from_cache() override;
361+
362 // Reply interface
363 void finished() override;
364 void error(std::exception_ptr ex) override;
365
366=== modified file 'src/scopes/internal/CategoryRegistry.cpp'
367--- src/scopes/internal/CategoryRegistry.cpp 2014-07-02 09:36:34 +0000
368+++ src/scopes/internal/CategoryRegistry.cpp 2015-02-05 09:36:37 +0000
369@@ -18,6 +18,8 @@
370
371 #include <unity/scopes/internal/CategoryRegistry.h>
372 #include <unity/UnityExceptions.h>
373+
374+#include <algorithm>
375 #include <sstream>
376
377 namespace unity
378@@ -32,13 +34,13 @@
379 void CategoryRegistry::register_category(Category::SCPtr category)
380 {
381 std::lock_guard<decltype(mutex_)> lock(mutex_);
382- if (categories_.find(category->id()) != categories_.end())
383+ auto id = category->id();
384+ auto it = find_if(categories_.begin(), categories_.end(), [id](const CatPair& p) { return p.first == id; });
385+ if (it != categories_.end())
386 {
387- std::ostringstream s;
388- s << "Category " << category->id() << " already defined";
389- throw InvalidArgumentException(s.str());
390+ throw InvalidArgumentException("register_category(): duplicate category: " + id);
391 }
392- categories_[category->id()] = category;
393+ categories_.push_back(make_pair(category->id(), category));
394 }
395
396 Category::SCPtr CategoryRegistry::register_category(VariantMap const& variant_map)
397@@ -58,7 +60,7 @@
398 Category::SCPtr CategoryRegistry::lookup_category(std::string const& id) const
399 {
400 std::lock_guard<decltype(mutex_)> lock(mutex_);
401- auto it = categories_.find(id);
402+ auto it = find_if(categories_.begin(), categories_.end(), [id](const CatPair& p) { return p.first == id; });
403 if (it != categories_.end())
404 {
405 return it->second;
406@@ -66,6 +68,17 @@
407 return nullptr;
408 }
409
410+VariantArray CategoryRegistry::serialize() const
411+{
412+ std::lock_guard<decltype(mutex_)> lock(mutex_);
413+ VariantArray va;
414+ for (auto&& p : categories_)
415+ {
416+ va.push_back(Variant(p.second->serialize()));
417+ }
418+ return va;
419+}
420+
421 } // namespace internal
422
423 } // namespace scopes
424
425=== modified file 'src/scopes/internal/QueryObject.cpp'
426--- src/scopes/internal/QueryObject.cpp 2015-01-09 03:16:51 +0000
427+++ src/scopes/internal/QueryObject.cpp 2015-02-05 09:36:37 +0000
428@@ -104,6 +104,7 @@
429 auto reply_proxy = make_shared<SearchReplyImpl>(reply,
430 self_,
431 cardinality_,
432+ search_query->query().query_string(),
433 search_query->department_id());
434 assert(reply_proxy);
435 reply_proxy_ = reply_proxy;
436
437=== modified file 'src/scopes/internal/RuntimeImpl.cpp'
438--- src/scopes/internal/RuntimeImpl.cpp 2015-01-20 06:27:04 +0000
439+++ src/scopes/internal/RuntimeImpl.cpp 2015-02-05 09:36:37 +0000
440@@ -529,6 +529,16 @@
441 }
442 }
443
444+string RuntimeImpl::cache_directory() const
445+{
446+ return find_cache_dir();
447+}
448+
449+string RuntimeImpl::tmp_directory() const
450+{
451+ return find_tmp_dir();
452+}
453+
454 string RuntimeImpl::demangled_id(string const& scope_id) const
455 {
456 // For scopes that are in a click package together with an app,
457
458=== modified file 'src/scopes/internal/SearchReplyImpl.cpp'
459--- src/scopes/internal/SearchReplyImpl.cpp 2015-01-09 03:16:51 +0000
460+++ src/scopes/internal/SearchReplyImpl.cpp 2015-02-05 09:36:37 +0000
461@@ -20,12 +20,18 @@
462 #include <unity/scopes/internal/SearchReplyImpl.h>
463
464 #include <unity/scopes/Annotation.h>
465-#include <unity/scopes/CategorisedResult.h>
466+#include <unity/scopes/internal/CategorisedResultImpl.h>
467 #include <unity/scopes/internal/DepartmentImpl.h>
468+#include <unity/scopes/internal/FilterBaseImpl.h>
469+#include <unity/scopes/internal/FilterStateImpl.h>
470 #include <unity/scopes/internal/MWReply.h>
471-#include <unity/scopes/internal/FilterBaseImpl.h>
472 #include <unity/scopes/internal/QueryObjectBase.h>
473+#include <unity/scopes/internal/RuntimeImpl.h>
474+#include <unity/scopes/ScopeExceptions.h>
475 #include <unity/UnityExceptions.h>
476+#include <unity/util/FileIO.h>
477+
478+#include <stdlib.h>
479
480 using namespace std;
481
482@@ -39,20 +45,24 @@
483 {
484
485 SearchReplyImpl::SearchReplyImpl(MWReplyProxy const& mw_proxy,
486- std::shared_ptr<QueryObjectBase> const& qo,
487+ shared_ptr<QueryObjectBase> const& qo,
488 int cardinality,
489- std::string const& current_department_id)
490+ string const& query_string,
491+ string const& current_department_id)
492 : ObjectImpl(mw_proxy)
493 , ReplyImpl(mw_proxy, qo)
494 , cat_registry_(new CategoryRegistry())
495 , cardinality_(cardinality)
496 , num_pushes_(0)
497+ , finished_(false)
498+ , query_string_(query_string)
499 , current_department_(current_department_id)
500 {
501 }
502
503 SearchReplyImpl::~SearchReplyImpl()
504 {
505+ finished();
506 }
507
508 void SearchReplyImpl::register_departments(Department::SCPtr const& parent)
509@@ -67,6 +77,12 @@
510 throw unity::LogicException("SearchReplyImpl::register_departments(): Failed to validate departments");
511 }
512
513+ if (query_string_.empty())
514+ {
515+ lock_guard<mutex> lock(mutex_);
516+ cached_departments_ = parent;
517+ }
518+
519 ReplyImpl::push(internal::DepartmentImpl::serialize_departments(parent)); // ignore return value?
520 }
521
522@@ -76,10 +92,10 @@
523 push(category);
524 }
525
526-Category::SCPtr SearchReplyImpl::register_category(std::string const& id,
527- std::string const& title,
528- std::string const &icon,
529- CategoryRenderer const& renderer_template)
530+Category::SCPtr SearchReplyImpl::register_category(string const& id,
531+ string const& title,
532+ string const &icon,
533+ CategoryRenderer const& renderer_template)
534 {
535 // will throw if adding same category again
536 auto cat = cat_registry_->register_category(id, title, icon, nullptr, renderer_template);
537@@ -87,11 +103,11 @@
538 return cat;
539 }
540
541-Category::SCPtr SearchReplyImpl::register_category(std::string const& id,
542- std::string const& title,
543- std::string const& icon,
544- CannedQuery const& query,
545- CategoryRenderer const& renderer_template)
546+Category::SCPtr SearchReplyImpl::register_category(string const& id,
547+ string const& title,
548+ string const& icon,
549+ CannedQuery const& query,
550+ CategoryRenderer const& renderer_template)
551 {
552 // will throw if adding same category again
553 auto cat = cat_registry_->register_category(id, title, icon, make_shared<CannedQuery>(query), renderer_template);
554@@ -99,7 +115,7 @@
555 return cat;
556 }
557
558-Category::SCPtr SearchReplyImpl::lookup_category(std::string const& id)
559+Category::SCPtr SearchReplyImpl::lookup_category(string const& id)
560 {
561 return cat_registry_->lookup_category(id);
562 }
563@@ -127,6 +143,14 @@
564 return false;
565 }
566
567+ // We cache the results of surfacing queries so, if a scope has not connectivity,
568+ // we can replay the results of the last successful surfacing query.
569+ if (query_string_.empty())
570+ {
571+ lock_guard<mutex> lock(mutex_);
572+ cached_results_.push_back(result);
573+ }
574+
575 // Enforce cardinality limit (0 means no limit). If the scope pushes more results
576 // than requested, future pushes are ignored. push() returns false
577 // on the last call that actually still pushed a result.
578@@ -153,6 +177,13 @@
579 throw unity::LogicException("SearchReplyImpl::push(): Failed to validate filters");
580 }
581
582+ if (query_string_.empty())
583+ {
584+ lock_guard<mutex> lock(mutex_);
585+ cached_filters_ = filters;
586+ cached_filter_state_ = filter_state;
587+ }
588+
589 VariantMap var;
590 var["filters"] = internal::FilterBaseImpl::serialize_filters(filters);
591 var["filter_state"] = filter_state.serialize();
592@@ -166,6 +197,220 @@
593 return ReplyImpl::push(var);
594 }
595
596+void SearchReplyImpl::finished()
597+{
598+ if (finished_.exchange(true))
599+ {
600+ return;
601+ }
602+ write_cached_results();
603+ ReplyImpl::finished(); // Upcall to deal with the normal case
604+}
605+
606+static constexpr char const* cache_file_name = ".surfacing_cache";
607+
608+void SearchReplyImpl::write_cached_results() noexcept
609+{
610+ assert(finished_);
611+
612+ if (!query_string_.empty())
613+ {
614+ return; // Caching applies only to surfacing queries
615+ }
616+
617+ string tmp_path;
618+ try
619+ {
620+ // Open a temporary file for writing.
621+ tmp_path = mw_proxy_->mw_base()->runtime()->cache_directory() + "/" + cache_file_name + "XXXXXX";
622+ auto opener = [tmp_path]()
623+ {
624+ int tmp_fd = mkstemp(const_cast<char*>(tmp_path.c_str()));
625+ if (tmp_fd == -1)
626+ {
627+ throw FileException("cannot open tmp file " + tmp_path, errno);
628+ }
629+ return tmp_fd;
630+ };
631+ auto closer = [tmp_path](int fd)
632+ {
633+ if (::close(fd) == -1)
634+ {
635+ // LCOV_EXCL_START
636+ throw FileException("cannot close tmp file " + tmp_path + " (fd = " + std::to_string(fd) + ")", errno);
637+ // LCOV_EXCL_STOP
638+ }
639+ };
640+ unity::util::ResourcePtr<int, decltype(closer)> tmp_file(opener(), closer);
641+
642+ // Turn departments, categories, and results into a JSON string.
643+ VariantMap departments;
644+ if (cached_departments_)
645+ {
646+ departments = cached_departments_->serialize();
647+ }
648+ auto filters = internal::FilterBaseImpl::serialize_filters(cached_filters_);
649+ auto filter_state = cached_filter_state_.serialize();
650+ VariantArray categories = cat_registry_->serialize();
651+ VariantArray results;
652+ for (auto const& r : cached_results_)
653+ {
654+ results.push_back(Variant(r.serialize()));
655+ }
656+ VariantMap vm;
657+ vm["departments"] = move(departments);
658+ vm["categories"] = move(categories);
659+ vm["filters"] = move(filters);
660+ vm["filter_state"] = move(filter_state);
661+ vm["results"] = move(results);
662+ string const json = Variant(move(vm)).serialize_json();
663+
664+ // Write tmp file.
665+ if (::write(tmp_file.get(), json.c_str(), json.size()) != static_cast<int>(json.size()))
666+ {
667+ // LCOV_EXCL_START
668+ throw FileException("cannot write tmp file " + tmp_path + " (fd = " + std::to_string(tmp_file.get()) + ")",
669+ errno);
670+ // LCOV_EXCL_STOP
671+ }
672+ tmp_file.dealloc(); // Close tmp file.
673+
674+ // Atomically replace the old cache with the new one.
675+ string cache_path = mw_proxy_->mw_base()->runtime()->cache_directory() + "/" + cache_file_name;
676+ if (rename(tmp_path.c_str(), cache_path.c_str()) == -1)
677+ {
678+ throw FileException("cannot rename tmp file " + tmp_path + " to " + cache_path, errno); // LCOV_EXCL_LINE
679+ }
680+ }
681+ catch (std::exception const& e)
682+ {
683+ ::unlink(tmp_path.c_str());
684+ BOOST_LOG(mw_proxy_->mw_base()->runtime()->logger())
685+ << "SearchReply::write_cached_results(): " << e.what();
686+ }
687+ // LCOV_EXCL_START
688+ catch (...)
689+ {
690+ ::unlink(tmp_path.c_str());
691+ BOOST_LOG(mw_proxy_->mw_base()->runtime()->logger())
692+ << "SearchReply::write_cached_results(): unknown exception";
693+ }
694+ // LCOV_EXCL_STOP
695+}
696+
697+void SearchReplyImpl::push_surfacing_results_from_cache() noexcept
698+{
699+ if (!query_string_.empty())
700+ {
701+ return; // Caching applies only to surfacing queries
702+ }
703+
704+ if (finished_.exchange(true))
705+ {
706+ return;
707+ }
708+
709+ string cache_path = mw_proxy_->mw_base()->runtime()->cache_directory() + "/" + cache_file_name;
710+ try
711+ {
712+ // Read cache file.
713+ string json;
714+ try
715+ {
716+ json = unity::util::read_text_file(cache_path);
717+ }
718+ catch (unity::FileException const& e)
719+ {
720+ if (e.error() == ENOENT)
721+ {
722+ ReplyImpl::finished();
723+ return; // No cache has been written yet.
724+ }
725+ throw;
726+ }
727+
728+ // Decode JSON for the three sections.
729+ Variant v(Variant::deserialize_json(json));
730+ VariantMap vm = v.get_dict();
731+
732+ auto it = vm.find("departments");
733+ if (it == vm.end())
734+ {
735+ throw unity::scopes::NotFoundException("malformed cache file", "departments");
736+ }
737+ auto department_dict = it->second.get_dict();
738+
739+ it = vm.find("categories");
740+ if (it == vm.end())
741+ {
742+ throw unity::scopes::NotFoundException("malformed cache file", "categories");
743+ }
744+ auto category_array = it->second.get_array();
745+
746+ it = vm.find("filters");
747+ if (it == vm.end())
748+ {
749+ throw unity::scopes::NotFoundException("malformed cache file", "filters");
750+ }
751+ auto filter_array = it->second.get_array();
752+
753+ it = vm.find("filter_state");
754+ if (it == vm.end())
755+ {
756+ throw unity::scopes::NotFoundException("malformed cache file", "filter_state");
757+ }
758+ auto filter_state_dict = it->second.get_dict();
759+
760+ it = vm.find("results");
761+ if (it == vm.end())
762+ {
763+ throw unity::scopes::NotFoundException("malformed cache file", "results");
764+ }
765+ auto result_array = it->second.get_array();
766+
767+ // We have the JSON strings as Variants, re-create the native representations
768+ // and re-instate them.
769+ if (!department_dict.empty())
770+ {
771+ auto departments = DepartmentImpl::create(move(department_dict));
772+ register_departments(move(departments));
773+ }
774+
775+ for (auto const& c : category_array)
776+ {
777+ // Can't use make_shared here because that isn't a friend of Category.
778+ auto cp = Category::SCPtr(new Category(move(c.get_dict())));
779+ register_category(cp);
780+ }
781+
782+ auto filters = FilterBaseImpl::deserialize_filters(move(filter_array));
783+ auto filter_state = FilterStateImpl::deserialize(move(filter_state_dict));
784+ push(filters, filter_state);
785+
786+ for (auto const& r : result_array)
787+ {
788+ VariantMap dict = r.get_dict();
789+ auto cr = CategorisedResult(new CategorisedResultImpl(*cat_registry_, dict));
790+ push(cr);
791+ }
792+ }
793+ catch (std::exception const& e)
794+ {
795+ BOOST_LOG(mw_proxy_->mw_base()->runtime()->logger())
796+ << "SearchReply::push_surfacing_results_from_cache() (file = " + cache_path + "): " << e.what();
797+ }
798+ // LCOV_EXCL_START
799+ catch (...)
800+ {
801+ BOOST_LOG(mw_proxy_->mw_base()->runtime()->logger())
802+ << "SearchReply::push_surfacing_results_from_cache() (file = " + cache_path + "): unknown exception";
803+ }
804+ // LCOV_EXCL_STOP
805+
806+ // Query is complete.
807+ ReplyImpl::finished();
808+}
809+
810 } // namespace internal
811
812 } // namespace scopes
813
814=== modified file 'src/scopes/internal/smartscopes/SSQueryObject.cpp'
815--- src/scopes/internal/smartscopes/SSQueryObject.cpp 2015-01-09 03:16:51 +0000
816+++ src/scopes/internal/smartscopes/SSQueryObject.cpp 2015-02-05 09:36:37 +0000
817@@ -207,6 +207,7 @@
818 q_reply_proxy = make_shared<SearchReplyImpl>(reply,
819 shared_from_this(),
820 query->q_cardinality,
821+ q_base->query().query_string(),
822 q_base->department_id());
823 assert(q_reply_proxy);
824 query->q_reply_proxy = q_reply_proxy;
825
826=== modified file 'src/scopes/testing/InProcessBenchmark.cpp'
827--- src/scopes/testing/InProcessBenchmark.cpp 2014-08-28 00:20:56 +0000
828+++ src/scopes/testing/InProcessBenchmark.cpp 2015-02-05 09:36:37 +0000
829@@ -208,6 +208,10 @@
830 {
831 return true;
832 }
833+
834+ void push_surfacing_results_from_cache() override
835+ {
836+ }
837 };
838
839 typedef std::chrono::high_resolution_clock Clock;
840
841=== modified file 'src/scopes/utility/internal/BufferedSearchReplyImpl.cpp'
842--- src/scopes/utility/internal/BufferedSearchReplyImpl.cpp 2014-12-01 01:51:38 +0000
843+++ src/scopes/utility/internal/BufferedSearchReplyImpl.cpp 2015-02-05 09:36:37 +0000
844@@ -92,6 +92,11 @@
845 return upstream_->push(filters, filter_state);
846 }
847
848+void BufferedSearchReplyImpl::push_surfacing_results_from_cache()
849+{
850+ upstream_->push_surfacing_results_from_cache();
851+}
852+
853 void BufferedSearchReplyImpl::finished()
854 {
855 upstream_->finished();
856
857=== modified file 'test/gtest/scopes/CMakeLists.txt'
858--- test/gtest/scopes/CMakeLists.txt 2015-01-26 08:20:45 +0000
859+++ test/gtest/scopes/CMakeLists.txt 2015-02-05 09:36:37 +0000
860@@ -27,6 +27,7 @@
861 add_subdirectory(RangeInputFilter)
862 add_subdirectory(RatingFilter)
863 add_subdirectory(ReplyReaper)
864+add_subdirectory(ResultCache)
865 add_subdirectory(SwitchFilter)
866 add_subdirectory(ValueSliderFilter)
867 add_subdirectory(PreviewWidget)
868
869=== added directory 'test/gtest/scopes/ResultCache'
870=== added file 'test/gtest/scopes/ResultCache/CMakeLists.txt'
871--- test/gtest/scopes/ResultCache/CMakeLists.txt 1970-01-01 00:00:00 +0000
872+++ test/gtest/scopes/ResultCache/CMakeLists.txt 2015-02-05 09:36:37 +0000
873@@ -0,0 +1,27 @@
874+configure_file(TestRegistry.ini.in ${CMAKE_CURRENT_BINARY_DIR}/TestRegistry.ini)
875+configure_file(Runtime.ini.in ${CMAKE_CURRENT_BINARY_DIR}/Runtime.ini)
876+configure_file(Zmq.ini.in ${CMAKE_CURRENT_BINARY_DIR}/Zmq.ini)
877+
878+add_definitions(-DTEST_SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}")
879+add_definitions(-DTEST_RUNTIME_PATH="${CMAKE_CURRENT_BINARY_DIR}")
880+add_definitions(-DTEST_RUNTIME_FILE="${CMAKE_CURRENT_BINARY_DIR}/Runtime.ini")
881+add_definitions(-DTEST_REGISTRY_PATH="${PROJECT_BINARY_DIR}/scoperegistry")
882+
883+add_executable(ResultCache_test ResultCache_test.cpp CacheScope.cpp)
884+target_link_libraries(ResultCache_test ${TESTLIBS})
885+
886+add_dependencies(ResultCache_test scoperegistry scoperunner)
887+
888+add_test(ResultCache ResultCache_test)
889+
890+set(SCOPE_DIR "${CMAKE_CURRENT_BINARY_DIR}/scopes")
891+
892+foreach (scope CacheScope AlwaysPushFromCacheScope)
893+ file(MAKE_DIRECTORY "${SCOPE_DIR}/${scope}")
894+ configure_file(CacheScope.ini.in ${SCOPE_DIR}/${scope}/${scope}.ini)
895+ add_library(${scope} MODULE CacheScope.cpp)
896+ set_target_properties(${scope}
897+ PROPERTIES
898+ LIBRARY_OUTPUT_DIRECTORY "${SCOPE_DIR}/${scope}/"
899+ )
900+endforeach()
901
902=== added file 'test/gtest/scopes/ResultCache/CacheScope.cpp'
903--- test/gtest/scopes/ResultCache/CacheScope.cpp 1970-01-01 00:00:00 +0000
904+++ test/gtest/scopes/ResultCache/CacheScope.cpp 2015-02-05 09:36:37 +0000
905@@ -0,0 +1,160 @@
906+/*
907+ * Copyright (C) 2015 Canonical Ltd
908+ *
909+ * This program is free software: you can redistribute it and/or modify
910+ * it under the terms of the GNU Lesser General Public License version 3 as
911+ * published by the Free Software Foundation.
912+ *
913+ * This program is distributed in the hope that it will be useful,
914+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
915+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
916+ * GNU Lesser General Public License for more details.
917+ *
918+ * You should have received a copy of the GNU Lesser General Public License
919+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
920+ *
921+ * Authored by: Michi Henning <michi.henning@canonical.com>
922+ */
923+
924+#include "CacheScope.h"
925+
926+#include <unity/scopes/CategorisedResult.h>
927+#include <unity/scopes/OptionSelectorFilter.h>
928+#include <unity/scopes/ScopeBase.h>
929+#include <unity/scopes/ScopeExceptions.h>
930+#include <unity/scopes/SearchReply.h>
931+
932+#include <boost/filesystem.hpp>
933+#include <unity/UnityExceptions.h>
934+#include <unity/util/FileIO.h>
935+
936+#include <atomic>
937+#include <mutex>
938+
939+using namespace std;
940+using namespace unity::scopes;
941+
942+namespace
943+{
944+
945+class TestQuery : public SearchQueryBase
946+{
947+public:
948+ TestQuery(CannedQuery const& query,
949+ SearchMetadata const& metadata,
950+ string const& id,
951+ RegistryProxy const& reg)
952+ : SearchQueryBase(query, metadata)
953+ , id_(id)
954+ , registry_(reg)
955+ {
956+ }
957+
958+ virtual void cancelled() override
959+ {
960+ }
961+
962+ virtual void run(SearchReplyProxy const& reply) override
963+ {
964+ if (id_ == "AlwaysPushFromCacheScope")
965+ {
966+ // So we get coverage on pushing from cache before a cache file exists.
967+ reply->push_surfacing_results_from_cache();
968+ return;
969+ }
970+
971+ if (query().query_string().empty())
972+ {
973+ // If there is a cache file, we use it to push.
974+ boost::system::error_code ec;
975+ if (boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec))
976+ {
977+ reply->push_surfacing_results_from_cache();
978+ // We do this twice, to get coverage on the double-call guard.
979+ reply->push_surfacing_results_from_cache();
980+ return;
981+ }
982+ }
983+ else if (query().query_string() == "non-empty from cache")
984+ {
985+ // This gives us coverage for the case that we are pushing from the cache for a non-empty query.
986+ reply->push_surfacing_results_from_cache();
987+ return;
988+ }
989+
990+ auto depts = Department::create("", query(), "Top");
991+ auto sub_dept = Department::create("sub" + query().query_string(), query(), "Sub");
992+ depts->add_subdepartment(move(sub_dept));
993+ reply->register_departments(move(depts));
994+
995+ Filters filters;
996+ OptionSelectorFilter::SPtr filter = OptionSelectorFilter::create("f1", "Choose an option", false);
997+ filter->add_option("o1", "Option 1");
998+ auto active_opt = filter->add_option("o2", "Option 2");
999+ FilterState fstate;
1000+ filter->update_state(fstate, active_opt, true);
1001+ filters.push_back(filter);
1002+ if (valid())
1003+ {
1004+ reply->push(filters, fstate);
1005+ }
1006+
1007+ auto cat = reply->register_category(id_, "", "");
1008+ CategorisedResult res(cat);
1009+ res.set_uri("uri");
1010+ res.set_title(query().query_string());
1011+ if (valid())
1012+ {
1013+ reply->push(res);
1014+ }
1015+ }
1016+
1017+private:
1018+ string id_;
1019+ RegistryProxy registry_;
1020+};
1021+
1022+} // namespace
1023+
1024+void CacheScope::start(string const& scope_id)
1025+{
1026+ lock_guard<mutex> lock(mutex_);
1027+ id_ = scope_id;
1028+}
1029+
1030+void CacheScope::stop()
1031+{
1032+}
1033+
1034+void CacheScope::run()
1035+{
1036+}
1037+
1038+SearchQueryBase::UPtr CacheScope::search(CannedQuery const& query, SearchMetadata const& metadata)
1039+{
1040+ lock_guard<mutex> lock(mutex_);
1041+ return SearchQueryBase::UPtr(new TestQuery(query, metadata, id_, registry()));
1042+}
1043+
1044+PreviewQueryBase::UPtr CacheScope::preview(Result const&, ActionMetadata const &)
1045+{
1046+ return nullptr; // unused
1047+}
1048+
1049+extern "C"
1050+{
1051+
1052+ unity::scopes::ScopeBase*
1053+ // cppcheck-suppress unusedFunction
1054+ UNITY_SCOPE_CREATE_FUNCTION()
1055+ {
1056+ return new CacheScope;
1057+ }
1058+
1059+ void
1060+ // cppcheck-suppress unusedFunction
1061+ UNITY_SCOPE_DESTROY_FUNCTION(unity::scopes::ScopeBase* scope_base)
1062+ {
1063+ delete scope_base;
1064+ }
1065+}
1066
1067=== added file 'test/gtest/scopes/ResultCache/CacheScope.h'
1068--- test/gtest/scopes/ResultCache/CacheScope.h 1970-01-01 00:00:00 +0000
1069+++ test/gtest/scopes/ResultCache/CacheScope.h 2015-02-05 09:36:37 +0000
1070@@ -0,0 +1,41 @@
1071+/*
1072+ * Copyright (C) 2015 Canonical Ltd
1073+ *
1074+ * This program is free software: you can redistribute it and/or modify
1075+ * it under the terms of the GNU Lesser General Public License version 3 as
1076+ * published by the Free Software Foundation.
1077+ *
1078+ * This program is distributed in the hope that it will be useful,
1079+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1080+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1081+ * GNU Lesser General Public License for more details.
1082+ *
1083+ * You should have received a copy of the GNU Lesser General Public License
1084+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1085+ *
1086+ * Authored by: Michi Henning <michi.henning@canonical.com>
1087+ */
1088+
1089+#pragma once
1090+
1091+#include <unity/scopes/ScopeBase.h>
1092+
1093+class CacheScope : public unity::scopes::ScopeBase
1094+{
1095+public:
1096+ virtual void start(std::string const&) override;
1097+
1098+ virtual void stop() override;
1099+
1100+ virtual void run() override;
1101+
1102+ virtual unity::scopes::SearchQueryBase::UPtr search(unity::scopes::CannedQuery const &,
1103+ unity::scopes::SearchMetadata const &) override;
1104+
1105+ virtual unity::scopes::PreviewQueryBase::UPtr preview(unity::scopes::Result const&,
1106+ unity::scopes::ActionMetadata const &) override;
1107+
1108+private:
1109+ std::string id_;
1110+ std::mutex mutex_;
1111+};
1112
1113=== added file 'test/gtest/scopes/ResultCache/CacheScope.ini.in'
1114--- test/gtest/scopes/ResultCache/CacheScope.ini.in 1970-01-01 00:00:00 +0000
1115+++ test/gtest/scopes/ResultCache/CacheScope.ini.in 2015-02-05 09:36:37 +0000
1116@@ -0,0 +1,4 @@
1117+[ScopeConfig]
1118+DisplayName = CacheScope
1119+Description = Scope that pushes cached results for surfacing query
1120+Author = Michi
1121
1122=== added file 'test/gtest/scopes/ResultCache/ResultCache_test.cpp'
1123--- test/gtest/scopes/ResultCache/ResultCache_test.cpp 1970-01-01 00:00:00 +0000
1124+++ test/gtest/scopes/ResultCache/ResultCache_test.cpp 2015-02-05 09:36:37 +0000
1125@@ -0,0 +1,414 @@
1126+/*
1127+ * Copyright (C) 2015 Canonical Ltd
1128+ *
1129+ * This program is free software: you can redistribute it and/or modify
1130+ * it under the terms of the GNU Lesser General Public License version 3 as
1131+ * published by the Free Software Foundation.
1132+ *
1133+ * This program is distributed in the hope that it will be useful,
1134+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1135+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1136+ * GNU Lesser General Public License for more details.
1137+ *
1138+ * You should have received a copy of the GNU Lesser General Public License
1139+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1140+ *
1141+ * Authored by: Michi Henning <michi.henning@canonical.com>
1142+ */
1143+
1144+#include <unity/scopes/CategorisedResult.h>
1145+#include <unity/scopes/internal/RegistryObject.h>
1146+#include <unity/scopes/internal/RuntimeImpl.h>
1147+#include <unity/scopes/internal/ScopeImpl.h>
1148+#include <unity/scopes/OptionSelectorFilter.h>
1149+
1150+#include <boost/filesystem.hpp>
1151+#include <gtest/gtest.h>
1152+
1153+#include "CacheScope.h"
1154+
1155+using namespace std;
1156+using namespace unity::scopes;
1157+using namespace unity::scopes::internal;
1158+
1159+class Receiver : public SearchListenerBase
1160+{
1161+public:
1162+ Receiver()
1163+ : query_complete_(false)
1164+ {
1165+ }
1166+
1167+ virtual void push(Department::SCPtr const& parent) override
1168+ {
1169+ lock_guard<mutex> lock(mutex_);
1170+ dept_ = parent;
1171+ }
1172+
1173+ virtual void push(Filters const& filters, FilterState const& filter_state) override
1174+ {
1175+ lock_guard<mutex> lock(mutex_);
1176+ filters_ = filters;
1177+ filter_state_ = filter_state;
1178+ }
1179+
1180+ virtual void push(CategorisedResult result) override
1181+ {
1182+ lock_guard<mutex> lock(mutex_);
1183+ result_ = make_shared<CategorisedResult>(result);
1184+ }
1185+
1186+ virtual void finished(CompletionDetails const& details) override
1187+ {
1188+ EXPECT_EQ(CompletionDetails::OK, details.status()) << details.message();
1189+ lock_guard<mutex> lock(mutex_);
1190+ query_complete_ = true;
1191+ cond_.notify_one();
1192+ }
1193+
1194+ void wait_until_finished()
1195+ {
1196+ unique_lock<mutex> lock(mutex_);
1197+ cond_.wait(lock, [this] { return this->query_complete_; });
1198+ query_complete_ = false;
1199+ }
1200+
1201+ Department::SCPtr dept() const
1202+ {
1203+ lock_guard<mutex> lock(mutex_);
1204+ return dept_;
1205+ }
1206+
1207+ Filters filters() const
1208+ {
1209+ lock_guard<mutex> lock(mutex_);
1210+ return filters_;
1211+ }
1212+
1213+ FilterState filter_state() const
1214+ {
1215+ lock_guard<mutex> lock(mutex_);
1216+ return filter_state_;
1217+ }
1218+
1219+ CategorisedResult::SCPtr result() const
1220+ {
1221+ lock_guard<mutex> lock(mutex_);
1222+ return result_;
1223+ }
1224+
1225+private:
1226+ Department::SCPtr dept_;
1227+ Filters filters_;
1228+ FilterState filter_state_;
1229+ CategorisedResult::SCPtr result_;
1230+ bool query_complete_;
1231+ mutable mutex mutex_;
1232+ condition_variable cond_;
1233+};
1234+
1235+class CacheScopeTest : public ::testing::Test
1236+{
1237+public:
1238+ CacheScopeTest()
1239+ {
1240+ runtime_ = Runtime::create(TEST_RUNTIME_FILE);
1241+ auto reg = runtime_->registry();
1242+ auto meta = reg->get_metadata("CacheScope");
1243+ scope_ = meta.proxy();
1244+ meta = reg->get_metadata("AlwaysPushFromCacheScope");
1245+ always_push_from_cache_scope_ = meta.proxy();
1246+ }
1247+
1248+ ScopeProxy scope() const
1249+ {
1250+ return scope_;
1251+ }
1252+
1253+ ScopeProxy always_push_from_cache_scope() const
1254+ {
1255+ return always_push_from_cache_scope_;
1256+ }
1257+
1258+private:
1259+ Runtime::UPtr runtime_;
1260+ ScopeProxy scope_;
1261+ ScopeProxy always_push_from_cache_scope_;
1262+};
1263+
1264+TEST_F(CacheScopeTest, push_from_cache_without_cache_file)
1265+{
1266+ ::unlink(TEST_RUNTIME_PATH "/unconfined/AlwaysPushFromCacheScope/.surfacing_cache");
1267+ auto receiver = make_shared<Receiver>();
1268+ always_push_from_cache_scope()->search("", SearchMetadata("unused", "unused"), receiver);
1269+ receiver->wait_until_finished();
1270+
1271+ auto r = receiver->result();
1272+ EXPECT_EQ(r, nullptr);
1273+}
1274+
1275+TEST_F(CacheScopeTest, non_surfacing_query)
1276+{
1277+ ::chmod(TEST_RUNTIME_PATH "/unconfined/CacheScope", 0700);
1278+ ::unlink(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1279+ auto receiver = make_shared<Receiver>();
1280+ scope()->search("some query", SearchMetadata("unused", "unused"), receiver);
1281+ receiver->wait_until_finished();
1282+
1283+ auto r = receiver->result();
1284+ EXPECT_EQ(r->title(), "some query");
1285+ auto d = receiver->dept();
1286+ EXPECT_EQ(d->id(), "");
1287+ auto sd = *d->subdepartments().begin();
1288+ EXPECT_EQ(sd->id(), "subsome query");
1289+
1290+ // Non-empty query, so there must be no cache file.
1291+ boost::system::error_code ec;
1292+ EXPECT_FALSE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1293+}
1294+
1295+TEST_F(CacheScopeTest, surfacing_query)
1296+{
1297+ ::unlink(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1298+ auto receiver = make_shared<Receiver>();
1299+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1300+ receiver->wait_until_finished();
1301+
1302+ auto r = receiver->result();
1303+ EXPECT_EQ(r->title(), "");
1304+ auto d = receiver->dept();
1305+ EXPECT_EQ(d->id(), "");
1306+ auto sd = *d->subdepartments().begin();
1307+ EXPECT_EQ(sd->id(), "sub");
1308+
1309+ // Empty query, so there must be a cache file.
1310+ boost::system::error_code ec;
1311+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1312+ system("cat " TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1313+}
1314+
1315+// Run another non-surfacing query before checking that the cache contains the
1316+// results of the last surfacing query.
1317+
1318+TEST_F(CacheScopeTest, non_surfacing_query2)
1319+{
1320+ auto receiver = make_shared<Receiver>();
1321+ scope()->search("some other query", SearchMetadata("unused", "unused"), receiver);
1322+ receiver->wait_until_finished();
1323+
1324+ auto r = receiver->result();
1325+ EXPECT_EQ(r->title(), "some other query");
1326+ auto d = receiver->dept();
1327+ EXPECT_EQ(d->id(), "");
1328+ auto sd = *d->subdepartments().begin();
1329+ EXPECT_EQ(sd->id(), "subsome other query");
1330+
1331+ // Cache file must still be there.
1332+ boost::system::error_code ec;
1333+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1334+}
1335+
1336+TEST_F(CacheScopeTest, push_from_cache)
1337+{
1338+ auto receiver = make_shared<Receiver>();
1339+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1340+ receiver->wait_until_finished();
1341+
1342+ auto r = receiver->result();
1343+ EXPECT_EQ(r->title(), "");
1344+ auto d = receiver->dept();
1345+ EXPECT_EQ(d->id(), "");
1346+ auto sd = *d->subdepartments().begin();
1347+ EXPECT_EQ(sd->id(), "sub");
1348+ auto filters = receiver->filters();
1349+ ASSERT_EQ(1, filters.size());
1350+ auto f = *filters.begin();
1351+ EXPECT_EQ("option_selector", f->filter_type());
1352+ auto osf = dynamic_pointer_cast<OptionSelectorFilter const>(f);
1353+ ASSERT_TRUE(osf != nullptr);
1354+ EXPECT_EQ("Choose an option", osf->label());
1355+ auto fs = receiver->filter_state();
1356+ EXPECT_TRUE(fs.has_filter("f1"));
1357+ EXPECT_TRUE(osf->has_active_option(fs));
1358+
1359+ // Cache file must still be there.
1360+ boost::system::error_code ec;
1361+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1362+}
1363+
1364+TEST_F(CacheScopeTest, push_non_empty_from_cache)
1365+{
1366+ auto receiver = make_shared<Receiver>();
1367+ scope()->search("non-empty from cache", SearchMetadata("unused", "unused"), receiver);
1368+ receiver->wait_until_finished();
1369+
1370+ // Cache file must still be there.
1371+ boost::system::error_code ec;
1372+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1373+}
1374+
1375+TEST_F(CacheScopeTest, write_failure)
1376+{
1377+ ::unlink(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1378+ ::chmod(TEST_RUNTIME_PATH "/unconfined/CacheScope", 0);
1379+ auto receiver = make_shared<Receiver>();
1380+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1381+ receiver->wait_until_finished();
1382+
1383+ // Empty query, but cache file could not be created
1384+ ::chmod(TEST_RUNTIME_PATH "/unconfined/CacheScope", 0700);
1385+ boost::system::error_code ec;
1386+ EXPECT_FALSE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1387+}
1388+
1389+TEST_F(CacheScopeTest, surfacing_query_2)
1390+{
1391+ auto receiver = make_shared<Receiver>();
1392+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1393+ receiver->wait_until_finished();
1394+
1395+ auto r = receiver->result();
1396+ EXPECT_EQ(r->title(), "");
1397+ auto d = receiver->dept();
1398+ EXPECT_EQ(d->id(), "");
1399+ auto sd = *d->subdepartments().begin();
1400+ EXPECT_EQ(sd->id(), "sub");
1401+ auto filters = receiver->filters();
1402+ ASSERT_EQ(1, filters.size());
1403+ auto f = *filters.begin();
1404+ EXPECT_EQ("option_selector", f->filter_type());
1405+ auto osf = dynamic_pointer_cast<OptionSelectorFilter const>(f);
1406+ ASSERT_TRUE(osf != nullptr);
1407+ EXPECT_EQ("Choose an option", osf->label());
1408+ auto fs = receiver->filter_state();
1409+ EXPECT_TRUE(fs.has_filter("f1"));
1410+ EXPECT_TRUE(osf->has_active_option(fs));
1411+
1412+ // New cache file must have been created
1413+ boost::system::error_code ec;
1414+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1415+}
1416+
1417+TEST_F(CacheScopeTest, read_failure)
1418+{
1419+ ::chmod(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", 0);
1420+ auto receiver = make_shared<Receiver>();
1421+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1422+ receiver->wait_until_finished();
1423+ EXPECT_EQ(receiver->result(), nullptr);
1424+ ::chmod(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", 0600);
1425+
1426+ // Cache file must still be there, but read will have failed.
1427+ boost::system::error_code ec;
1428+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1429+}
1430+
1431+// Stop warnings about unused return value from system()
1432+
1433+#pragma GCC diagnostic push
1434+#pragma GCC diagnostic ignored "-Wunused-result"
1435+
1436+TEST_F(CacheScopeTest, missing_departments)
1437+{
1438+ // Get coverage on missing departments entry
1439+ system("cp " TEST_SOURCE_PATH "/no_dept_cache " TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1440+ auto receiver = make_shared<Receiver>();
1441+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1442+ receiver->wait_until_finished();
1443+ EXPECT_EQ(receiver->result(), nullptr);
1444+
1445+ // Cache file must still be there, but decode will have failed.
1446+ boost::system::error_code ec;
1447+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1448+}
1449+
1450+TEST_F(CacheScopeTest, missing_categories)
1451+{
1452+ // Get coverage on missing categories entry
1453+ system("cp " TEST_SOURCE_PATH "/no_cat_cache " TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1454+ auto receiver = make_shared<Receiver>();
1455+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1456+ receiver->wait_until_finished();
1457+ EXPECT_EQ(receiver->result(), nullptr);
1458+
1459+ // Cache file must still be there, but decode will have failed.
1460+ boost::system::error_code ec;
1461+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1462+}
1463+
1464+TEST_F(CacheScopeTest, missing_filters)
1465+{
1466+ // Get coverage on missing filters entry
1467+ system("cp " TEST_SOURCE_PATH "/no_filters_cache " TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1468+ auto receiver = make_shared<Receiver>();
1469+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1470+ receiver->wait_until_finished();
1471+ EXPECT_EQ(receiver->result(), nullptr);
1472+
1473+ // Cache file must still be there, but decode will have failed.
1474+ boost::system::error_code ec;
1475+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1476+}
1477+
1478+TEST_F(CacheScopeTest, missing_filter_state)
1479+{
1480+ // Get coverage on missing filters_state entry
1481+ system("cp " TEST_SOURCE_PATH "/no_filter_state_cache " TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1482+ auto receiver = make_shared<Receiver>();
1483+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1484+ receiver->wait_until_finished();
1485+ EXPECT_EQ(receiver->result(), nullptr);
1486+
1487+ // Cache file must still be there, but decode will have failed.
1488+ boost::system::error_code ec;
1489+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1490+}
1491+
1492+TEST_F(CacheScopeTest, missing_results)
1493+{
1494+ // Get coverage on missing results entry
1495+ system("cp " TEST_SOURCE_PATH "/no_results_cache " TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache");
1496+ auto receiver = make_shared<Receiver>();
1497+ scope()->search("", SearchMetadata("unused", "unused"), receiver);
1498+ receiver->wait_until_finished();
1499+ EXPECT_EQ(receiver->result(), nullptr);
1500+
1501+ // Cache file must still be there, but decode will have failed.
1502+ boost::system::error_code ec;
1503+ EXPECT_TRUE(boost::filesystem::exists(TEST_RUNTIME_PATH "/unconfined/CacheScope/.surfacing_cache", ec));
1504+}
1505+
1506+#pragma GCC diagnostic pop
1507+
1508+int main(int argc, char **argv)
1509+{
1510+ ::testing::InitGoogleTest(&argc, argv);
1511+
1512+ int rc = 0;
1513+
1514+ // Set the "TEST_DESKTOP_FILES_DIR" env var before forking as not to create desktop files in ~/.local
1515+ putenv(const_cast<char*>("TEST_DESKTOP_FILES_DIR=" TEST_RUNTIME_PATH));
1516+
1517+ auto rpid = fork();
1518+ if (rpid == 0)
1519+ {
1520+ const char* const args[] = {"scoperegistry [CacheScope_test]", TEST_RUNTIME_FILE, nullptr};
1521+ if (execv(TEST_REGISTRY_PATH "/scoperegistry", const_cast<char* const*>(args)) < 0)
1522+ {
1523+ perror("Error starting scoperegistry:");
1524+ }
1525+ return 1;
1526+ }
1527+ else if (rpid > 0)
1528+ {
1529+ rc = RUN_ALL_TESTS();
1530+ kill(rpid, SIGTERM);
1531+ waitpid(rpid, nullptr, 0);
1532+ }
1533+ else
1534+ {
1535+ perror("Failed to fork:");
1536+ }
1537+
1538+ return rc;
1539+}
1540
1541=== added file 'test/gtest/scopes/ResultCache/Runtime.ini.in'
1542--- test/gtest/scopes/ResultCache/Runtime.ini.in 1970-01-01 00:00:00 +0000
1543+++ test/gtest/scopes/ResultCache/Runtime.ini.in 2015-02-05 09:36:37 +0000
1544@@ -0,0 +1,8 @@
1545+[Runtime]
1546+Registry.Identity = TestRegistry
1547+Registry.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/TestRegistry.ini
1548+Default.Middleware = Zmq
1549+Zmq.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/Zmq.ini
1550+Smartscopes.Registry.Identity =
1551+CacheDir=@CMAKE_CURRENT_BINARY_DIR@
1552+LogDir=
1553
1554=== added file 'test/gtest/scopes/ResultCache/TestRegistry.ini.in'
1555--- test/gtest/scopes/ResultCache/TestRegistry.ini.in 1970-01-01 00:00:00 +0000
1556+++ test/gtest/scopes/ResultCache/TestRegistry.ini.in 2015-02-05 09:36:37 +0000
1557@@ -0,0 +1,7 @@
1558+[Registry]
1559+Middleware = Zmq
1560+Zmq.ConfigFile = @CMAKE_CURRENT_BINARY_DIR@/Zmq.ini
1561+Scope.InstallDir = @CMAKE_CURRENT_BINARY_DIR@/scopes
1562+OEM.InstallDir = /unused
1563+Click.InstallDir = @CMAKE_CURRENT_BINARY_DIR@/click
1564+Scoperunner.Path = @PROJECT_BINARY_DIR@/scoperunner/scoperunner
1565
1566=== added file 'test/gtest/scopes/ResultCache/Zmq.ini.in'
1567--- test/gtest/scopes/ResultCache/Zmq.ini.in 1970-01-01 00:00:00 +0000
1568+++ test/gtest/scopes/ResultCache/Zmq.ini.in 2015-02-05 09:36:37 +0000
1569@@ -0,0 +1,2 @@
1570+[Zmq]
1571+EndpointDir = /tmp
1572
1573=== added file 'test/gtest/scopes/ResultCache/no_cat_cache'
1574--- test/gtest/scopes/ResultCache/no_cat_cache 1970-01-01 00:00:00 +0000
1575+++ test/gtest/scopes/ResultCache/no_cat_cache 2015-02-05 09:36:37 +0000
1576@@ -0,0 +1,1 @@
1577+{"departments":{"departments":[{"label":"Sub","query":{"department_id":"sub","filter_state":{},"query_string":"","scope":"CacheScope"}}],"label":"Top","query":{"department_id":"","filter_state":{},"query_string":"","scope":"CacheScope"}},"filter_state":{"f1":["o2"]},"filters":[{"filter_type":"option_selector","id":"f1","label":"Choose an option","multi_select":false,"options":[{"id":"o1","label":"Option 1"},{"id":"o2","label":"Option 2"}]}],"results":[{"attrs":{"title":"","uri":"uri"},"internal":{"cat_id":"CacheScope"}}]}
1578
1579=== added file 'test/gtest/scopes/ResultCache/no_dept_cache'
1580--- test/gtest/scopes/ResultCache/no_dept_cache 1970-01-01 00:00:00 +0000
1581+++ test/gtest/scopes/ResultCache/no_dept_cache 2015-02-05 09:36:37 +0000
1582@@ -0,0 +1,1 @@
1583+{"categories":[{"icon":"","id":"CacheScope","renderer_template":"\n {\n \"schema-version\":1,\n \"template\":\n {\n \"category-layout\":\"grid\"\n },\n \"components\":\n {\n \"title\":\"title\",\n \"art\":\"art\"\n }\n }\n ","title":""}],"filter_state":{"f1":["o2"]},"filters":[{"filter_type":"option_selector","id":"f1","label":"Choose an option","multi_select":false,"options":[{"id":"o1","label":"Option 1"},{"id":"o2","label":"Option 2"}]}],"results":[{"attrs":{"title":"","uri":"uri"},"internal":{"cat_id":"CacheScope"}}]}
1584
1585=== added file 'test/gtest/scopes/ResultCache/no_filter_state_cache'
1586--- test/gtest/scopes/ResultCache/no_filter_state_cache 1970-01-01 00:00:00 +0000
1587+++ test/gtest/scopes/ResultCache/no_filter_state_cache 2015-02-05 09:36:37 +0000
1588@@ -0,0 +1,1 @@
1589+{"categories":[{"icon":"","id":"CacheScope","renderer_template":"\n {\n \"schema-version\":1,\n \"template\":\n {\n \"category-layout\":\"grid\"\n },\n \"components\":\n {\n \"title\":\"title\",\n \"art\":\"art\"\n }\n }\n ","title":""}],"departments":{"departments":[{"label":"Sub","query":{"department_id":"sub","filter_state":{},"query_string":"","scope":"CacheScope"}}],"label":"Top","query":{"department_id":"","filter_state":{},"query_string":"","scope":"CacheScope"}},"filters":[{"filter_type":"option_selector","id":"f1","label":"Choose an option","multi_select":false,"options":[{"id":"o1","label":"Option 1"},{"id":"o2","label":"Option 2"}]}],"results":[{"attrs":{"title":"","uri":"uri"},"internal":{"cat_id":"CacheScope"}}]}
1590
1591=== added file 'test/gtest/scopes/ResultCache/no_filters_cache'
1592--- test/gtest/scopes/ResultCache/no_filters_cache 1970-01-01 00:00:00 +0000
1593+++ test/gtest/scopes/ResultCache/no_filters_cache 2015-02-05 09:36:37 +0000
1594@@ -0,0 +1,1 @@
1595+{"categories":[{"icon":"","id":"CacheScope","renderer_template":"\n {\n \"schema-version\":1,\n \"template\":\n {\n \"category-layout\":\"grid\"\n },\n \"components\":\n {\n \"title\":\"title\",\n \"art\":\"art\"\n }\n }\n ","title":""}],"departments":{"departments":[{"label":"Sub","query":{"department_id":"sub","filter_state":{},"query_string":"","scope":"CacheScope"}}],"label":"Top","query":{"department_id":"","filter_state":{},"query_string":"","scope":"CacheScope"}},"filter_state":{"f1":["o2"]},"results":[{"attrs":{"title":"","uri":"uri"},"internal":{"cat_id":"CacheScope"}}]}
1596
1597=== added file 'test/gtest/scopes/ResultCache/no_results_cache'
1598--- test/gtest/scopes/ResultCache/no_results_cache 1970-01-01 00:00:00 +0000
1599+++ test/gtest/scopes/ResultCache/no_results_cache 2015-02-05 09:36:37 +0000
1600@@ -0,0 +1,1 @@
1601+{"categories":[{"icon":"","id":"CacheScope","renderer_template":"\n {\n \"schema-version\":1,\n \"template\":\n {\n \"category-layout\":\"grid\"\n },\n \"components\":\n {\n \"title\":\"title\",\n \"art\":\"art\"\n }\n }\n ","title":""}],"departments":{"departments":[{"label":"Sub","query":{"department_id":"sub","filter_state":{},"query_string":"","scope":"CacheScope"}}],"label":"Top","query":{"department_id":"","filter_state":{},"query_string":"","scope":"CacheScope"}},"filter_state":{"f1":["o2"]},"filters":[{"filter_type":"option_selector","id":"f1","label":"Choose an option","multi_select":false,"options":[{"id":"o1","label":"Option 1"},{"id":"o2","label":"Option 2"}]}]}
1602
1603=== added directory 'test/gtest/scopes/ResultCache/unconfined'
1604=== added directory 'test/gtest/scopes/ResultCache/unconfined/CacheScope'
1605=== modified file 'test/gtest/scopes/internal/RuntimeConfig/RuntimeConfig_test.cpp'
1606--- test/gtest/scopes/internal/RuntimeConfig/RuntimeConfig_test.cpp 2015-01-09 03:16:51 +0000
1607+++ test/gtest/scopes/internal/RuntimeConfig/RuntimeConfig_test.cpp 2015-02-05 09:36:37 +0000
1608@@ -23,6 +23,7 @@
1609
1610 #include <unity/UnityExceptions.h>
1611
1612+#include <boost/regex.hpp> // Use Boost implementation until http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53631 is fixed.
1613 #include <gtest/gtest.h>
1614
1615 using namespace std;
1616@@ -230,12 +231,16 @@
1617 }
1618 catch (ConfigException const& e)
1619 {
1620- EXPECT_STREQ( "unity::scopes::ConfigException: \"" TEST_SRC_DIR "/NoLogDir.ini\": "
1621- "No LogDir configured and failed to get default:\n"
1622- " unity::ResourceException: RuntimeConfig::default_log_directory(): $HOME not set:\n"
1623- " unity::LogicException: Could not get string value (" TEST_SRC_DIR "/NoLogDir.ini, "
1624- "group: Runtime): Key file does not have key 'LogDir' in group 'Runtime'",
1625- e.what());
1626+ // Using regex here because error message returned by glib changed from Utopic to Vivid.
1627+ // The final .* takes care of the difference. Note that, instead of using TEST_SRC_DIR, we
1628+ // use .+. That's because, when building with bzr bd, we end up with a '+' in the path,
1629+ // and that is a regex metacharacter, causing the match to fail.
1630+ boost::regex r("unity::scopes::ConfigException: \".+/NoLogDir.ini\": "
1631+ "No LogDir configured and failed to get default:\\n"
1632+ " unity::ResourceException: RuntimeConfig::default_log_directory\\(\\): \\$HOME not set:\\n"
1633+ " unity::LogicException: Could not get string value \\(.+/NoLogDir.ini, "
1634+ "group: Runtime\\): Key file does not have key 'LogDir' in group 'Runtime'");
1635+ EXPECT_TRUE(boost::regex_match(e.what(), r)) << e.what();
1636 }
1637
1638 try
1639@@ -247,9 +252,9 @@
1640 }
1641 catch (ConfigException const& e)
1642 {
1643- EXPECT_STREQ( "unity::scopes::ConfigException: \"" TEST_SRC_DIR "/NoAppDir.ini\": "
1644- "No AppDir configured and failed to get default:\n"
1645- " unity::ResourceException: RuntimeConfig::default_app_directory(): $HOME not set",
1646+ EXPECT_STREQ("unity::scopes::ConfigException: \"" TEST_SRC_DIR "/NoAppDir.ini\": "
1647+ "No AppDir configured and failed to get default:\n"
1648+ " unity::ResourceException: RuntimeConfig::default_app_directory(): $HOME not set",
1649 e.what());
1650 }
1651

Subscribers

People subscribed via source and target branches

to all changes: