Merge lp:~michihenning/unity-scopes-api/cache-results into lp:unity-scopes-api
- cache-results
- Merge into trunk
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 | ||||
Related bugs: |
|
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:
|
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->
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->
Note: This is change is ABI compatible with gcc and clang despite the addition of a new virtual function.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
Re-submitting a proposal causes launchpad to nuke the commit message. Neat…
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
Marking this as work in progress until we get the go-ahead for an ABI-breaking change.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:575
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 576. By Michi Henning
-
Merged trunk and resolved conflicts.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:576
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:576
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:576
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Paweł Stołowski (stolowski) wrote : | # |
279 + std::vector<
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) CategorisedResu
and:
2) bool SearchReplyImpl
{
// If this is an aggregator scope, it may be pushing result items obtained
// from ReplyObject without registering a category first.
if (cat_registry_
{
(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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Paweł Stołowski (stolowski) wrote : | # |
> 1) CategorisedResu
> const& reg, VariantMap const& variant_map)
>
> and:
>
> 2) bool SearchReplyImpl
> {
> // If this is an aggregator scope, it may be pushing result items obtained
> // from ReplyObject without registering a category first.
> if (cat_registry_
> {
> register_
>
> (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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Paweł Stołowski (stolowski) wrote : | # |
824 + VariantMap dict = r.get_dict();
825 + string const cat_id = dict.at(
826 + auto cat = lookup_
827 + auto crip = new CategorisedResu
828 + auto cr = CategorisedResu
I think this could be simplified by constructing CategorisedResult using this ctor:
CategorisedResu
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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:
Other than the above this looks really cool, I like it a lot!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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) CategorisedResu
> const& reg, VariantMap const& variant_map)
>
> and:
>
> 2) bool SearchReplyImpl
> {
> // If this is an aggregator scope, it may be pushing result items obtained
> // from ReplyObject without registering a category first.
> if (cat_registry_
> {
> register_
>
> (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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
> I think this could be simplified by constructing CategorisedResult using this
> ctor:
> CategorisedResu
> reg, VariantMap const& variant_map)
Fixed, thanks for that!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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:
> 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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:573
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 :)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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:
> 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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:578
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 579. By Michi Henning
-
Added filters and filter_state to cached results and added corresponding tests.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:579
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Paweł Stołowski (stolowski) wrote : | # |
Great, thank you!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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]
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
- 580. By Michi Henning
-
Moved push_surfacing_
results_ from_cache to end of virtual functions to
avoid ABI break.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Paweł Stołowski (stolowski) wrote : | # |
Michi, could you please merge trunk?
- 581. By Michi Henning
-
Merged trunk and resolved conflicts.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michi Henning (michihenning) wrote : | # |
> Michi, could you please merge trunk?
Done.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
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 |
FAILED: Continuous integration, rev:572 /code.launchpad .net/~michihenn ing/unity- scopes- api/cache- results/ +merge/ 246257/ +edit-commit- message
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:/
http:// jenkins. qa.ubuntu. com/job/ unity-scopes- api-ci/ 505/ jenkins. qa.ubuntu. com/job/ unity-scopes- api-vivid- amd64-ci/ 30/console jenkins. qa.ubuntu. com/job/ unity-scopes- api-vivid- armhf-ci/ 30/console jenkins. qa.ubuntu. com/job/ unity-scopes- api-vivid- i386-ci/ 30/console
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- scopes- api-ci/ 505/rebuild
http://