Merge lp:~stolowski/unity-scope-click/header-apps into lp:unity-scope-click/devel
- header-apps
- Merge into devel
Status: | Merged |
---|---|
Approved by: | dobey |
Approved revision: | 320 |
Merged at revision: | 317 |
Proposed branch: | lp:~stolowski/unity-scope-click/header-apps |
Merge into: | lp:unity-scope-click/devel |
Diff against target: |
981 lines (+509/-126) 17 files modified
CMakeLists.txt (+4/-3) cmake/UseGSettings.cmake (+42/-0) data/CMakeLists.txt (+2/-0) data/com.canonical.unity.clickscope.gschema.xml (+10/-0) debian/control (+1/-0) debian/unity-scope-click.install (+1/-0) libclickscope/click/CMakeLists.txt (+4/-0) libclickscope/click/configuration.cpp (+37/-0) libclickscope/click/configuration.h (+16/-0) libclickscope/tests/test_configuration.cpp (+27/-0) scope/clickapps/apps-query.cpp (+132/-61) scope/clickapps/apps-query.h (+29/-6) scope/clickapps/apps-scope.cpp (+2/-1) scope/tests/CMakeLists.txt (+44/-0) scope/tests/test_apps_query.cpp (+84/-0) scope/tests/test_helpers.h (+71/-0) scope/tests/test_query.cpp (+3/-55) |
To merge this branch: | bzr merge lp:~stolowski/unity-scope-click/header-apps |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Alejandro J. Cura (community) | Approve | ||
Review via email: mp+225844@code.launchpad.net |
Commit message
Display preloaded core apps at the top in a dedicated headerless category. The list of apps to be displayed can be overriden with a dconf key.
Description of the change
Display preloaded core apps at the top in a dedicated headerless category. The list of apps to be displayed can be overriden with a dconf key.
PS Jenkins bot (ps-jenkins) wrote : | # |
Alejandro J. Cura (alecu) wrote : | # |
Code looks good; seems to be missing libgsettings-qt-dev in debian/control
Will test it on device after jenkins approves.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:320
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alejandro J. Cura (alecu) wrote : | # |
Tested on mako, looks wonderful.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-06-23 15:00:28 +0000 |
3 | +++ CMakeLists.txt 2014-07-08 07:23:41 +0000 |
4 | @@ -20,6 +20,7 @@ |
5 | set(APPS_DATA_DIR ${CMAKE_INSTALL_FULL_DATADIR}/unity/scopes/clickapps/) |
6 | |
7 | include(FindPkgConfig) |
8 | +include(UseGSettings) |
9 | |
10 | pkg_check_modules(UNITY_SCOPES REQUIRED libunity-scopes>=0.5.0 libunity-api>=0.1.3) |
11 | add_definitions(${UNITY_SCOPES_CFLAGS} ${UNITY_SCOPES_CFLAGS_OTHER}) |
12 | @@ -75,7 +76,7 @@ |
13 | |
14 | # Custom targets for the tests |
15 | add_custom_target (test |
16 | - DEPENDS test-click-scope test-libclickscope |
17 | + DEPENDS test-click-scope test-apps-scope test-libclickscope |
18 | ) |
19 | |
20 | add_custom_target (test-disabled |
21 | @@ -90,13 +91,13 @@ |
22 | |
23 | # Add a custom target for running the tests under valgrind. |
24 | add_custom_target (test-valgrind |
25 | - DEPENDS test-click-scope-valgrind test-libclickscope-valgrind |
26 | + DEPENDS test-click-scope-valgrind test-apps-scope-valgrind test-libclickscope-valgrind |
27 | ) |
28 | |
29 | # Add a custom target for running the tests under valgrind with the |
30 | # full leak checks enabled. |
31 | add_custom_target (test-leaks |
32 | - DEPENDS test-click-scope-leaks test-libclickscope-leaks |
33 | + DEPENDS test-click-scope-leaks test-apps-scope-leaks test-libclickscope-leaks |
34 | ) |
35 | |
36 | # Also let "make check" and partners work. |
37 | |
38 | === added file 'cmake/UseGSettings.cmake' |
39 | --- cmake/UseGSettings.cmake 1970-01-01 00:00:00 +0000 |
40 | +++ cmake/UseGSettings.cmake 2014-07-08 07:23:41 +0000 |
41 | @@ -0,0 +1,42 @@ |
42 | +# GSettings.cmake, CMake macros written for Marlin, feel free to re-use them. |
43 | + |
44 | +option (GSETTINGS_LOCALINSTALL "Install GSettings Schemas locally instead of to the GLib prefix" ${LOCAL_INSTALL}) |
45 | + |
46 | +option (GSETTINGS_COMPILE "Compile GSettings Schemas after installation" ${GSETTINGS_LOCALINSTALL}) |
47 | + |
48 | +if(GSETTINGS_LOCALINSTALL) |
49 | + message(STATUS "GSettings schemas will be installed locally.") |
50 | +endif() |
51 | + |
52 | +if(GSETTINGS_COMPILE) |
53 | + message(STATUS "GSettings shemas will be compiled.") |
54 | +endif() |
55 | + |
56 | +macro(add_schema SCHEMA_NAME) |
57 | + |
58 | + set(PKG_CONFIG_EXECUTABLE pkg-config) |
59 | + # Have an option to not install the schema into where GLib is |
60 | + if (GSETTINGS_LOCALINSTALL) |
61 | + SET (GSETTINGS_DIR "${CMAKE_INSTALL_PREFIX}/share/glib-2.0/schemas/") |
62 | + else (GSETTINGS_LOCALINSTALL) |
63 | + execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} glib-2.0 --variable prefix OUTPUT_VARIABLE _glib_prefix OUTPUT_STRIP_TRAILING_WHITESPACE) |
64 | + SET (GSETTINGS_DIR "${_glib_prefix}/share/glib-2.0/schemas/") |
65 | + endif (GSETTINGS_LOCALINSTALL) |
66 | + |
67 | + # Run the validator and error if it fails |
68 | + execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas OUTPUT_VARIABLE _glib_comple_schemas OUTPUT_STRIP_TRAILING_WHITESPACE) |
69 | + execute_process (COMMAND ${_glib_comple_schemas} --dry-run --schema-file=${CMAKE_CURRENT_SOURCE_DIR}/${SCHEMA_NAME} ERROR_VARIABLE _schemas_invalid OUTPUT_STRIP_TRAILING_WHITESPACE) |
70 | + |
71 | + if (_schemas_invalid) |
72 | + message (SEND_ERROR "Schema validation error: ${_schemas_invalid}") |
73 | + endif (_schemas_invalid) |
74 | + |
75 | + # Actually install and recomple schemas |
76 | + message (STATUS "GSettings schemas will be installed into ${GSETTINGS_DIR}") |
77 | + install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/${SCHEMA_NAME} DESTINATION ${GSETTINGS_DIR} OPTIONAL) |
78 | + |
79 | + if (GSETTINGS_COMPILE) |
80 | + install (CODE "message (STATUS \"Compiling GSettings schemas\")") |
81 | + install (CODE "execute_process (COMMAND ${_glib_comple_schemas} ${GSETTINGS_DIR})") |
82 | + endif () |
83 | +endmacro() |
84 | |
85 | === modified file 'data/CMakeLists.txt' |
86 | --- data/CMakeLists.txt 2014-06-30 21:50:00 +0000 |
87 | +++ data/CMakeLists.txt 2014-07-08 07:23:41 +0000 |
88 | @@ -2,6 +2,8 @@ |
89 | set(STORE_INI_TARGET com.canonical.scopes.clickstore.ini) |
90 | set(APPS_INI_TARGET clickscope.ini) |
91 | |
92 | +add_schema(com.canonical.unity.clickscope.gschema.xml) |
93 | + |
94 | configure_file( |
95 | ${STORE_INI_TARGET}.in.in |
96 | ${STORE_INI_TARGET}.in |
97 | |
98 | === added file 'data/com.canonical.unity.clickscope.gschema.xml' |
99 | --- data/com.canonical.unity.clickscope.gschema.xml 1970-01-01 00:00:00 +0000 |
100 | +++ data/com.canonical.unity.clickscope.gschema.xml 2014-07-08 07:23:41 +0000 |
101 | @@ -0,0 +1,10 @@ |
102 | +<?xml version="1.0" encoding="UTF-8"?> |
103 | +<schemalist> |
104 | + <schema path="/com/canonical/unity/clickscope/" id="com.canonical.Unity.ClickScope" gettext-domain="unity-scope-click"> |
105 | + <key type="as" name="core-apps"> |
106 | + <default>[]</default> |
107 | + <summary>Applications to display in the top category of the Apps scope</summary> |
108 | + <description>List of application IDs that will be displayed in the upper area of the Applications scope for quick access.</description> |
109 | + </key> |
110 | + </schema> |
111 | +</schemalist> |
112 | |
113 | === modified file 'debian/control' |
114 | --- debian/control 2014-06-23 15:00:28 +0000 |
115 | +++ debian/control 2014-07-08 07:23:41 +0000 |
116 | @@ -14,6 +14,7 @@ |
117 | libubuntuoneauth-2.0-dev, |
118 | libunity-api-dev (>= 7.80.7), |
119 | libunity-scopes-dev (>= 0.5.0), |
120 | + libgsettings-qt-dev, |
121 | pkg-config, |
122 | python3-all, |
123 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
124 | |
125 | === modified file 'debian/unity-scope-click.install' |
126 | --- debian/unity-scope-click.install 2014-06-20 20:00:53 +0000 |
127 | +++ debian/unity-scope-click.install 2014-07-08 07:23:41 +0000 |
128 | @@ -1,4 +1,5 @@ |
129 | usr/lib/*/unity-scopes/* |
130 | usr/lib/unity-scope-click/* |
131 | usr/share/unity/scopes/* |
132 | +usr/share/glib-2.0/schemas/* |
133 | usr/share/locale/*/LC_MESSAGES/unity-scope-click.mo |
134 | |
135 | === modified file 'libclickscope/click/CMakeLists.txt' |
136 | --- libclickscope/click/CMakeLists.txt 2014-06-23 15:00:28 +0000 |
137 | +++ libclickscope/click/CMakeLists.txt 2014-07-08 07:23:41 +0000 |
138 | @@ -2,11 +2,13 @@ |
139 | SET (CMAKE_AUTOMOC ON) |
140 | find_package (Qt5Core REQUIRED) |
141 | pkg_check_modules(JSON_CPP REQUIRED jsoncpp) |
142 | +pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt) |
143 | |
144 | add_definitions( |
145 | -DGETTEXT_PACKAGE=\"${PROJECT_NAME}\" |
146 | -DGETTEXT_LOCALEDIR=\"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}\" |
147 | -DCLICK_INSTALL_HELPER=\"${CMAKE_INSTALL_PREFIX}/lib/unity-scope-click/install-helper\" |
148 | + ${GSETTINGS_QT_CFLAGS} ${GSETTINGS_QT_OTHER} |
149 | ) |
150 | |
151 | add_library(${SCOPE_LIB_NAME} STATIC |
152 | @@ -32,6 +34,7 @@ |
153 | |
154 | include_directories( |
155 | ${JSON_CPP_INCLUDE_DIRS} |
156 | + ${GSETTINGS_QT_INCLUDE_DIRS} |
157 | ${CMAKE_SOURCE_DIR}/libclickscope |
158 | ) |
159 | |
160 | @@ -41,5 +44,6 @@ |
161 | ${UBUNTUONE_LDFLAGS} |
162 | ${UBUNTU_DOWNLOAD_MANAGER_CLIENT_LDFLAGS} |
163 | ${UBUNTU_DOWNLOAD_MANAGER_COMMON_LDFLAGS} |
164 | + ${GSETTINGS_QT_LIBRARIES} |
165 | -lboost_locale |
166 | ) |
167 | |
168 | === modified file 'libclickscope/click/configuration.cpp' |
169 | --- libclickscope/click/configuration.cpp 2014-06-25 21:33:59 +0000 |
170 | +++ libclickscope/click/configuration.cpp 2014-07-08 07:23:41 +0000 |
171 | @@ -32,6 +32,11 @@ |
172 | |
173 | #include <QDir> |
174 | #include <QProcess> |
175 | +#include <QStringList> |
176 | +#include <QVariant> |
177 | +#include <QDebug> |
178 | + |
179 | +#include <qgsettings.h> |
180 | |
181 | #include <boost/algorithm/string.hpp> |
182 | #include <boost/algorithm/string/replace.hpp> |
183 | @@ -141,4 +146,36 @@ |
184 | != FULL_LANG_CODES.end(); |
185 | } |
186 | |
187 | +const std::vector<std::string> Configuration::get_dconf_strings(const std::string& schema, const std::string& key) const |
188 | +{ |
189 | + if (!QGSettings::isSchemaInstalled(schema.c_str())) |
190 | + { |
191 | + qWarning() << "Schema" << QString::fromStdString(schema) << "is missing"; |
192 | + return std::vector<std::string>(); |
193 | + } |
194 | + QGSettings qgs(schema.c_str()); |
195 | + std::vector<std::string> v; |
196 | + if (qgs.keys().contains(QString::fromStdString(key))) |
197 | + { |
198 | + auto locations = qgs.get(QString::fromStdString(key)).toStringList(); |
199 | + for(const auto& l : locations) { |
200 | + v.push_back(l.toStdString()); |
201 | + } |
202 | + } |
203 | + else |
204 | + { |
205 | + qWarning() << "No" << QString::fromStdString(key) << " key in schema" << QString::fromStdString(schema); |
206 | + } |
207 | + return v; |
208 | +} |
209 | + |
210 | +const std::vector<std::string> Configuration::get_core_apps() const |
211 | +{ |
212 | + auto apps = get_dconf_strings(Configuration::COREAPPS_SCHEMA, Configuration::COREAPPS_KEY); |
213 | + if (apps.empty()) { |
214 | + apps = get_default_core_apps(); |
215 | + } |
216 | + return apps; |
217 | +} |
218 | + |
219 | } // namespace click |
220 | |
221 | === modified file 'libclickscope/click/configuration.h' |
222 | --- libclickscope/click/configuration.h 2014-06-25 21:33:59 +0000 |
223 | +++ libclickscope/click/configuration.h 2014-07-08 07:23:41 +0000 |
224 | @@ -54,10 +54,26 @@ |
225 | virtual std::string get_accept_languages(); |
226 | static bool is_full_lang_code(const std::string& language); |
227 | |
228 | + constexpr static const char* COREAPPS_SCHEMA {"com.canonical.Unity.ClickScope"}; |
229 | + constexpr static const char* COREAPPS_KEY {"coreApps"}; |
230 | + |
231 | + virtual const std::vector<std::string> get_core_apps() const; |
232 | virtual ~Configuration() {} |
233 | protected: |
234 | virtual std::vector<std::string> list_folder(const std::string &folder, const std::string &pattern); |
235 | virtual std::string architectureFromDpkg(); |
236 | + virtual const std::vector<std::string> get_dconf_strings(const std::string& schema, const std::string& key) const; |
237 | + static const std::vector<std::string>& get_default_core_apps() { |
238 | + static std::vector<std::string> default_apps { |
239 | + "dialer-app", |
240 | + "messaging-app", |
241 | + "com.ubuntu.calculator", |
242 | + "com.ubuntu.clock", |
243 | + "com.ubuntu.camera", |
244 | + "com.ubuntu.calendar" |
245 | + }; |
246 | + return default_apps; |
247 | + } |
248 | }; |
249 | |
250 | } // namespace click |
251 | |
252 | === modified file 'libclickscope/tests/test_configuration.cpp' |
253 | --- libclickscope/tests/test_configuration.cpp 2014-06-25 21:33:59 +0000 |
254 | +++ libclickscope/tests/test_configuration.cpp 2014-07-08 07:23:41 +0000 |
255 | @@ -27,6 +27,8 @@ |
256 | * files in the program, then also delete it here. |
257 | */ |
258 | |
259 | +#include <QStringList> |
260 | + |
261 | #include <gmock/gmock.h> |
262 | #include <gtest/gtest.h> |
263 | |
264 | @@ -42,10 +44,35 @@ |
265 | public: |
266 | MOCK_METHOD2(list_folder, std::vector<std::string>( |
267 | const std::string& folder, const std::string& pattern)); |
268 | + MOCK_CONST_METHOD2(get_dconf_strings, const std::vector<std::string>(const std::string& schema, const std::string& key)); |
269 | + using Configuration::get_default_core_apps; |
270 | }; |
271 | |
272 | } |
273 | |
274 | +TEST(Configuration, getCoreAppsFound) |
275 | +{ |
276 | + using namespace ::testing; |
277 | + FakeConfiguration c; |
278 | + EXPECT_CALL(c, get_dconf_strings(Configuration::COREAPPS_SCHEMA, |
279 | + Configuration::COREAPPS_KEY)) |
280 | + .WillOnce(Return(std::vector<std::string>{"package1", "package2"})); |
281 | + auto found_apps = c.get_core_apps(); |
282 | + auto expected_apps = std::vector<std::string>{"package1", "package2"}; |
283 | + ASSERT_EQ(found_apps, expected_apps); |
284 | +} |
285 | + |
286 | +TEST(Configuration, getCoreAppsEmpty) |
287 | +{ |
288 | + using namespace ::testing; |
289 | + FakeConfiguration c; |
290 | + EXPECT_CALL(c, get_dconf_strings(Configuration::COREAPPS_SCHEMA, |
291 | + Configuration::COREAPPS_KEY)) |
292 | + .WillOnce(Return(std::vector<std::string>{})); |
293 | + auto found_apps = c.get_core_apps(); |
294 | + auto expected_apps = c.get_default_core_apps(); |
295 | + ASSERT_EQ(found_apps, expected_apps); |
296 | +} |
297 | |
298 | TEST(Configuration, getAvailableFrameworksUsesRightFolder) |
299 | { |
300 | |
301 | === modified file 'scope/clickapps/apps-query.cpp' |
302 | --- scope/clickapps/apps-query.cpp 2014-06-30 20:06:33 +0000 |
303 | +++ scope/clickapps/apps-query.cpp 2014-07-08 07:23:41 +0000 |
304 | @@ -42,6 +42,7 @@ |
305 | |
306 | #include <click/click-i18n.h> |
307 | #include "apps-query.h" |
308 | +#include <QDebug> |
309 | |
310 | namespace |
311 | { |
312 | @@ -64,24 +65,6 @@ |
313 | } |
314 | )"; |
315 | |
316 | -std::string CATEGORY_APPS_SEARCH = R"( |
317 | - { |
318 | - "schema-version" : 1, |
319 | - "template" : { |
320 | - "category-layout" : "grid", |
321 | - "card-layout" : "horizontal", |
322 | - "card-size": "large" |
323 | - }, |
324 | - "components" : { |
325 | - "title" : "title", |
326 | - "mascot" : { |
327 | - "field": "art" |
328 | - }, |
329 | - "subtitle": "publisher" |
330 | - } |
331 | - } |
332 | -)"; |
333 | - |
334 | static const char CATEGORY_STORE[] = R"( |
335 | { |
336 | "template": { |
337 | @@ -104,51 +87,138 @@ |
338 | |
339 | } |
340 | |
341 | -void click::Query::push_local_results(scopes::SearchReplyProxy const &replyProxy, |
342 | - std::vector<click::Application> const &apps, |
343 | - std::string &categoryTemplate) |
344 | -{ |
345 | - scopes::CategoryRenderer rdr(categoryTemplate); |
346 | +click::apps::ResultPusher::ResultPusher(const scopes::SearchReplyProxy &replyProxy, const std::vector<std::string>& core_apps) |
347 | + : replyProxy(replyProxy), |
348 | + core_apps(core_apps), |
349 | + top_apps_lookup(core_apps.begin(), core_apps.end()) |
350 | +{ |
351 | +} |
352 | + |
353 | +void click::apps::ResultPusher::push_result(scopes::Category::SCPtr& cat, const click::Application& a) |
354 | +{ |
355 | + scopes::CategorisedResult res(cat); |
356 | + res.set_title(a.title); |
357 | + res.set_art(a.icon_url); |
358 | + res.set_uri(a.url); |
359 | + res[click::apps::Query::ResultKeys::NAME] = a.name; |
360 | + res[click::apps::Query::ResultKeys::DESCRIPTION] = a.description; |
361 | + res[click::apps::Query::ResultKeys::MAIN_SCREENSHOT] = a.main_screenshot; |
362 | + res[click::apps::Query::ResultKeys::INSTALLED] = true; |
363 | + res[click::apps::Query::ResultKeys::VERSION] = a.version; |
364 | + replyProxy->push(res); |
365 | +} |
366 | + |
367 | +// |
368 | +// Return an application identifier used to match applications against core-apps dconf key; |
369 | +// For click apps, it just returns application name (e.g. com.canonical.calculator). |
370 | +// For non-click apps, it return the desktop file name (without extension), taken from app uri. |
371 | +std::string click::apps::ResultPusher::get_app_identifier(const click::Application& app) |
372 | +{ |
373 | + static const std::string app_prefix("application:///"); |
374 | + if (!app.name.empty()) |
375 | + { |
376 | + return app.name; |
377 | + } |
378 | + if (app.url.size() > app_prefix.size()) |
379 | + { |
380 | + auto i = app.url.rfind('.'); |
381 | + if (i != std::string::npos) |
382 | + { |
383 | + return app.url.substr(app_prefix.size(), i - app_prefix.size()); |
384 | + } |
385 | + } |
386 | + throw std::runtime_error("Cannot determine application identifier for" + app.url); |
387 | +} |
388 | + |
389 | +void click::apps::ResultPusher::push_local_results( |
390 | + const std::vector<click::Application> &apps, |
391 | + const std::string &categoryTemplate) |
392 | +{ |
393 | + const scopes::CategoryRenderer rdr(categoryTemplate); |
394 | auto cat = replyProxy->register_category("local", _("My apps"), "", rdr); |
395 | |
396 | for(const auto & a: apps) |
397 | { |
398 | - scopes::CategorisedResult res(cat); |
399 | - res.set_title(a.title); |
400 | - res.set_art(a.icon_url); |
401 | - res.set_uri(a.url); |
402 | - res[click::Query::ResultKeys::NAME] = a.name; |
403 | - res[click::Query::ResultKeys::DESCRIPTION] = a.description; |
404 | - res[click::Query::ResultKeys::MAIN_SCREENSHOT] = a.main_screenshot; |
405 | - res[click::Query::ResultKeys::INSTALLED] = true; |
406 | - res[click::Query::ResultKeys::VERSION] = a.version; |
407 | - replyProxy->push(res); |
408 | - } |
409 | -} |
410 | - |
411 | -struct click::Query::Private |
412 | -{ |
413 | - Private(click::Index& index, const scopes::SearchMetadata& metadata) |
414 | - : index(index), |
415 | - meta(metadata) |
416 | - { |
417 | - } |
418 | - click::Index& index; |
419 | + try |
420 | + { |
421 | + if (top_apps_lookup.size() == 0 || top_apps_lookup.find(get_app_identifier(a)) == top_apps_lookup.end()) |
422 | + { |
423 | + push_result(cat, a); |
424 | + } |
425 | + } |
426 | + catch (const std::runtime_error &e) |
427 | + { |
428 | + qWarning() << QString::fromStdString(e.what()); |
429 | + } |
430 | + } |
431 | +} |
432 | + |
433 | +void click::apps::ResultPusher::push_top_results( |
434 | + const std::vector<click::Application>& apps, |
435 | + const std::string& categoryTemplate) |
436 | +{ |
437 | + const scopes::CategoryRenderer rdr(categoryTemplate); |
438 | + auto cat = replyProxy->register_category("predefined", "", "", rdr); |
439 | + |
440 | + // |
441 | + // iterate over all apps, insert those matching core apps into top_apps_to_push |
442 | + std::map<std::string, click::Application> top_apps_to_push; |
443 | + for (const auto& a: apps) |
444 | + { |
445 | + try |
446 | + { |
447 | + const auto id = get_app_identifier(a); |
448 | + if (top_apps_lookup.find(id) != top_apps_lookup.end()) |
449 | + { |
450 | + top_apps_to_push[id] = a; |
451 | + if (core_apps.size() == top_apps_to_push.size()) |
452 | + { |
453 | + // no need to iterate over remaining apps |
454 | + break; |
455 | + } |
456 | + } |
457 | + } |
458 | + catch (const std::runtime_error &e) |
459 | + { |
460 | + qWarning() << QString::fromStdString(e.what()); |
461 | + } |
462 | + } |
463 | + |
464 | + // |
465 | + // iterate over core apps and insert them based on top_apps_to_push; |
466 | + // this way the order of core apps is preserved. |
467 | + for (const auto &a: core_apps) |
468 | + { |
469 | + auto const it = top_apps_to_push.find(a); |
470 | + if (it != top_apps_to_push.end()) |
471 | + { |
472 | + push_result(cat, it->second); |
473 | + } |
474 | + } |
475 | +} |
476 | + |
477 | +struct click::apps::Query::Private |
478 | +{ |
479 | + Private(const scopes::SearchMetadata& metadata) |
480 | + : meta(metadata) |
481 | + { |
482 | + } |
483 | scopes::SearchMetadata meta; |
484 | + click::Configuration configuration; |
485 | }; |
486 | |
487 | -click::Query::Query(unity::scopes::CannedQuery const& query, click::Index& index, scopes::SearchMetadata const& metadata) |
488 | +click::apps::Query::Query(unity::scopes::CannedQuery const& query, scopes::SearchMetadata const& metadata) |
489 | : unity::scopes::SearchQueryBase(query, metadata), |
490 | - impl(new Private(index, metadata)) |
491 | + impl(new Private(metadata)) |
492 | { |
493 | } |
494 | |
495 | -void click::Query::cancelled() |
496 | +void click::apps::Query::cancelled() |
497 | { |
498 | qDebug() << "cancelling search of" << QString::fromStdString(query().query_string()); |
499 | } |
500 | |
501 | -click::Query::~Query() |
502 | +click::apps::Query::~Query() |
503 | { |
504 | qDebug() << "destroying search"; |
505 | } |
506 | @@ -165,7 +235,7 @@ |
507 | |
508 | } |
509 | |
510 | -void click::Query::add_fake_store_app(scopes::SearchReplyProxy const& searchReply) |
511 | +void click::apps::Query::add_fake_store_app(scopes::SearchReplyProxy const& searchReply) |
512 | { |
513 | static const std::string title = _("Ubuntu Store"); |
514 | static const std::string cat_title = _("Get more apps from the store"); |
515 | @@ -185,27 +255,28 @@ |
516 | res.set_title(title); |
517 | res.set_art(STORE_DATA_DIR "/store-scope-icon.svg"); |
518 | res.set_uri(store_scope.to_uri()); |
519 | - res[click::Query::ResultKeys::NAME] = title; |
520 | - res[click::Query::ResultKeys::DESCRIPTION] = ""; |
521 | - res[click::Query::ResultKeys::MAIN_SCREENSHOT] = ""; |
522 | - res[click::Query::ResultKeys::INSTALLED] = true; |
523 | - res[click::Query::ResultKeys::VERSION] = ""; |
524 | + res[click::apps::Query::ResultKeys::NAME] = title; |
525 | + res[click::apps::Query::ResultKeys::DESCRIPTION] = ""; |
526 | + res[click::apps::Query::ResultKeys::MAIN_SCREENSHOT] = ""; |
527 | + res[click::apps::Query::ResultKeys::INSTALLED] = true; |
528 | + res[click::apps::Query::ResultKeys::VERSION] = ""; |
529 | searchReply->push(res); |
530 | } |
531 | } |
532 | |
533 | -void click::Query::run(scopes::SearchReplyProxy const& searchReply) |
534 | +void click::apps::Query::run(scopes::SearchReplyProxy const& searchReply) |
535 | { |
536 | + const std::string categoryTemplate = CATEGORY_APPS_DISPLAY; |
537 | auto querystr = query().query_string(); |
538 | - std::string categoryTemplate = CATEGORY_APPS_SEARCH; |
539 | + |
540 | + ResultPusher pusher(searchReply, querystr.empty() ? impl->configuration.get_core_apps() : std::vector<std::string>()); |
541 | + auto localResults = clickInterfaceInstance().find_installed_apps(querystr); |
542 | + |
543 | if (querystr.empty()) { |
544 | - categoryTemplate = CATEGORY_APPS_DISPLAY; |
545 | + pusher.push_top_results(localResults, categoryTemplate); |
546 | } |
547 | - auto localResults = clickInterfaceInstance().find_installed_apps( |
548 | - querystr); |
549 | |
550 | - push_local_results( |
551 | - searchReply, |
552 | + pusher.push_local_results( |
553 | localResults, |
554 | categoryTemplate); |
555 | |
556 | |
557 | === modified file 'scope/clickapps/apps-query.h' |
558 | --- scope/clickapps/apps-query.h 2014-05-28 14:27:28 +0000 |
559 | +++ scope/clickapps/apps-query.h 2014-07-08 07:23:41 +0000 |
560 | @@ -37,14 +37,19 @@ |
561 | |
562 | #include <QSharedPointer> |
563 | #include <set> |
564 | +#include <unordered_set> |
565 | |
566 | |
567 | namespace click |
568 | { |
569 | |
570 | class Application; |
571 | +class Configuration; |
572 | class Index; |
573 | |
574 | +namespace apps |
575 | +{ |
576 | + |
577 | class Query : public scopes::SearchQueryBase |
578 | { |
579 | public: |
580 | @@ -60,7 +65,7 @@ |
581 | constexpr static const char* VERSION{"version"}; |
582 | }; |
583 | |
584 | - Query(unity::scopes::CannedQuery const& query, click::Index& index, scopes::SearchMetadata const& metadata); |
585 | + Query(unity::scopes::CannedQuery const& query, scopes::SearchMetadata const& metadata); |
586 | virtual ~Query(); |
587 | |
588 | virtual void cancelled() override; |
589 | @@ -69,14 +74,32 @@ |
590 | |
591 | protected: |
592 | virtual void add_fake_store_app(scopes::SearchReplyProxy const &replyProxy); |
593 | - virtual void push_local_results(scopes::SearchReplyProxy const &replyProxy, |
594 | - std::vector<click::Application> const &apps, |
595 | - std::string& categoryTemplate); |
596 | - |
597 | private: |
598 | struct Private; |
599 | QSharedPointer<Private> impl; |
600 | }; |
601 | -} |
602 | + |
603 | +class ResultPusher |
604 | +{ |
605 | + const scopes::SearchReplyProxy &replyProxy; |
606 | + std::vector<std::string> core_apps; |
607 | + std::unordered_set<std::string> top_apps_lookup; |
608 | + |
609 | +public: |
610 | + ResultPusher(const scopes::SearchReplyProxy &replyProxy, const std::vector<std::string>& core_apps); |
611 | + virtual ~ResultPusher() = default; |
612 | + |
613 | + virtual void push_local_results(const std::vector<click::Application> &apps, |
614 | + const std::string& categoryTemplate); |
615 | + |
616 | + virtual void push_top_results( |
617 | + const std::vector<click::Application>& apps, |
618 | + const std::string& categoryTemplate); |
619 | +protected: |
620 | + virtual void push_result(scopes::Category::SCPtr& cat, const click::Application& a); |
621 | + static std::string get_app_identifier(const click::Application& app); |
622 | +}; |
623 | +} // namespace apps |
624 | +} // namespace query |
625 | |
626 | #endif // CLICK_QUERY_H |
627 | |
628 | === modified file 'scope/clickapps/apps-scope.cpp' |
629 | --- scope/clickapps/apps-scope.cpp 2014-06-18 16:23:50 +0000 |
630 | +++ scope/clickapps/apps-scope.cpp 2014-07-08 07:23:41 +0000 |
631 | @@ -42,6 +42,7 @@ |
632 | #include "apps-scope.h" |
633 | #include "apps-query.h" |
634 | |
635 | +using namespace click; |
636 | |
637 | click::Scope::Scope() |
638 | { |
639 | @@ -78,7 +79,7 @@ |
640 | |
641 | scopes::SearchQueryBase::UPtr click::Scope::search(unity::scopes::CannedQuery const& q, scopes::SearchMetadata const& metadata) |
642 | { |
643 | - return scopes::SearchQueryBase::UPtr(new click::Query(q, *index, metadata)); |
644 | + return scopes::SearchQueryBase::UPtr(new click::apps::Query(q, metadata)); |
645 | } |
646 | |
647 | |
648 | |
649 | === modified file 'scope/tests/CMakeLists.txt' |
650 | --- scope/tests/CMakeLists.txt 2014-06-23 15:00:28 +0000 |
651 | +++ scope/tests/CMakeLists.txt 2014-07-08 07:23:41 +0000 |
652 | @@ -1,4 +1,5 @@ |
653 | set (CLICKSCOPE_TESTS_TARGET click-scope-tests) |
654 | +set (APPS_SCOPE_TESTS_TARGET apps-scope-tests) |
655 | find_package(Threads) |
656 | |
657 | # Qt5 bits |
658 | @@ -19,7 +20,12 @@ |
659 | test_query.cpp |
660 | ) |
661 | |
662 | +add_executable (${APPS_SCOPE_TESTS_TARGET} |
663 | + test_apps_query.cpp |
664 | +) |
665 | + |
666 | qt5_use_modules(${CLICKSCOPE_TESTS_TARGET} Core DBus Network Test) |
667 | +qt5_use_modules(${APPS_SCOPE_TESTS_TARGET} Core DBus Network Test) |
668 | |
669 | target_link_libraries(${CLICKSCOPE_TESTS_TARGET} |
670 | ${STORE_LIB_UNVERSIONED} |
671 | @@ -37,6 +43,22 @@ |
672 | ${CMAKE_THREAD_LIBS_INIT} |
673 | ) |
674 | |
675 | +target_link_libraries(${APPS_SCOPE_TESTS_TARGET} |
676 | + ${APPS_LIB_UNVERSIONED} |
677 | + ${SCOPE_LIB_NAME} |
678 | + |
679 | + ${UNITY_SCOPES_LDFLAGS} |
680 | + ${UBUNTUONE_LDFLAGS} |
681 | + ${UBUNTU_DOWNLOAD_MANAGER_CLIENT_LDFLAGS} |
682 | + ${UBUNTU_DOWNLOAD_MANAGER_COMMON_LDFLAGS} |
683 | + ${JSON_CPP_LDFLAGS} |
684 | + |
685 | + gmock |
686 | + gmock_main |
687 | + |
688 | + ${CMAKE_THREAD_LIBS_INIT} |
689 | +) |
690 | + |
691 | add_custom_target (test-click-scope |
692 | COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${CLICKSCOPE_TESTS_TARGET} |
693 | DEPENDS ${CLICKSCOPE_TESTS_TARGET} |
694 | @@ -56,6 +78,28 @@ |
695 | DEPENDS ${CLICKSCOPE_TESTS_TARGET} |
696 | ) |
697 | |
698 | +# --- |
699 | + |
700 | +add_custom_target (test-apps-scope |
701 | + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${APPS_SCOPE_TESTS_TARGET} |
702 | + DEPENDS ${APPS_SCOPE_TESTS_TARGET} |
703 | +) |
704 | + |
705 | +add_custom_target(test-apps-scope-valgrind |
706 | + COMMAND valgrind --tool=memcheck ${CMAKE_CURRENT_BINARY_DIR}/${APPS_SCOPE_TESTS_TARGET} |
707 | + DEPENDS ${APPS_SCOPE_TESTS_TARGET} |
708 | +) |
709 | + |
710 | +add_custom_target(test-apps-scope-leaks |
711 | + COMMAND valgrind --tool=memcheck --track-origins=yes --num-callers=40 --leak-resolution=high --leak-check=full ${CMAKE_CURRENT_BINARY_DIR}/${APPS_SCOPE_TESTS_TARGET} |
712 | + DEPENDS ${APPS_SCOPE_TESTS_TARGET} |
713 | +) |
714 | +add_custom_target (test-apps-scope-disabled |
715 | + COMMAND GTEST_ALSO_RUN_DISABLED_TESTS=1 ${CMAKE_CURRENT_BINARY_DIR}/${APPS_SCOPE_TESTS_TARGET} |
716 | + DEPENDS ${APPS_SCOPE_TESTS_TARGET} |
717 | +) |
718 | + |
719 | + |
720 | add_subdirectory(integration) |
721 | add_subdirectory(download_manager_tool) |
722 | add_subdirectory(click_interface_tool) |
723 | |
724 | === added file 'scope/tests/test_apps_query.cpp' |
725 | --- scope/tests/test_apps_query.cpp 1970-01-01 00:00:00 +0000 |
726 | +++ scope/tests/test_apps_query.cpp 2014-07-08 07:23:41 +0000 |
727 | @@ -0,0 +1,84 @@ |
728 | +/* |
729 | + * Copyright (C) 2014 Canonical Ltd. |
730 | + * |
731 | + * This program is free software: you can redistribute it and/or modify it |
732 | + * under the terms of the GNU General Public License version 3, as published |
733 | + * by the Free Software Foundation. |
734 | + * |
735 | + * This program is distributed in the hope that it will be useful, but |
736 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
737 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
738 | + * PURPOSE. See the GNU General Public License for more details. |
739 | + * |
740 | + * You should have received a copy of the GNU General Public License along |
741 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
742 | + * |
743 | + * In addition, as a special exception, the copyright holders give |
744 | + * permission to link the code of portions of this program with the |
745 | + * OpenSSL library under certain conditions as described in each |
746 | + * individual source file, and distribute linked combinations |
747 | + * including the two. |
748 | + * You must obey the GNU General Public License in all respects |
749 | + * for all of the code used other than OpenSSL. If you modify |
750 | + * file(s) with this exception, you may extend this exception to your |
751 | + * version of the file(s), but you are not obligated to do so. If you |
752 | + * do not wish to do so, delete this exception statement from your |
753 | + * version. If you delete this exception statement from all source |
754 | + * files in the program, then also delete it here. |
755 | + */ |
756 | + |
757 | +#include <string> |
758 | +#include <memory> |
759 | + |
760 | +#include <gtest/gtest.h> |
761 | +#include <gmock/gmock.h> |
762 | + |
763 | +#include <clickapps/apps-query.h> |
764 | + |
765 | +#include <unity/scopes/SearchReply.h> |
766 | +#include <unity/scopes/testing/MockSearchReply.h> |
767 | + |
768 | +#include "test_helpers.h" |
769 | + |
770 | +using namespace click::test::helpers; |
771 | +using namespace ::testing; |
772 | + |
773 | +class ResultPusherTest : public ::testing::Test |
774 | +{ |
775 | +protected: |
776 | + scopes::SearchReplyProxy reply; |
777 | +public: |
778 | + ResultPusherTest() |
779 | + { |
780 | + reply.reset(new scopes::testing::MockSearchReply()); |
781 | + } |
782 | +}; |
783 | + |
784 | +MATCHER_P(HasApplicationTitle, n, "") { return arg["title"].get_string() == n; } |
785 | + |
786 | +TEST_F(ResultPusherTest, testPushTopAndLocalResults) |
787 | +{ |
788 | + std::string categoryTemplate("{}"); |
789 | + std::vector<click::Application> apps { |
790 | + {"app1", "App1", 0.0f, "icon", "url", "", "sshot"}, |
791 | + {"app2", "App2", 0.0f, "icon", "url", "", "sshot"}, |
792 | + {"app3", "App3", 0.0f, "icon", "url", "", "sshot"}, |
793 | + {"", "App4", 0.0f, "icon", "application:///app4.desktop", "", "sshot"} // a non-click app |
794 | + }; |
795 | + |
796 | + click::apps::ResultPusher pusher(reply, {"app2", "app4"}); |
797 | + auto mockreply = (scopes::testing::MockSearchReply*)reply.get(); |
798 | + |
799 | + scopes::CategoryRenderer renderer("{}"); |
800 | + auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer); |
801 | + |
802 | + EXPECT_CALL(*mockreply, register_category(_, _, _, _)).WillRepeatedly(Return(ptrCat)); |
803 | + EXPECT_CALL(*mockreply, push(Matcher<unity::scopes::CategorisedResult const&>(HasApplicationTitle(std::string("App2"))))); |
804 | + EXPECT_CALL(*mockreply, push(Matcher<unity::scopes::CategorisedResult const&>(HasApplicationTitle(std::string("App4"))))); |
805 | + |
806 | + EXPECT_CALL(*mockreply, push(Matcher<unity::scopes::CategorisedResult const&>(HasApplicationTitle(std::string("App1"))))); |
807 | + EXPECT_CALL(*mockreply, push(Matcher<unity::scopes::CategorisedResult const&>(HasApplicationTitle(std::string("App3"))))); |
808 | + pusher.push_top_results(apps, categoryTemplate); |
809 | + pusher.push_local_results(apps, categoryTemplate); |
810 | +} |
811 | + |
812 | |
813 | === added file 'scope/tests/test_helpers.h' |
814 | --- scope/tests/test_helpers.h 1970-01-01 00:00:00 +0000 |
815 | +++ scope/tests/test_helpers.h 2014-07-08 07:23:41 +0000 |
816 | @@ -0,0 +1,71 @@ |
817 | +#ifndef TEST_HELPERS_H |
818 | +#define TEST_HELPERS_H |
819 | + |
820 | +#include <click/department-lookup.h> |
821 | +#include <click/highlights.h> |
822 | +#include "click/index.h" |
823 | +#include <click/interface.h> |
824 | +#include <click/package.h> |
825 | + |
826 | +namespace click |
827 | +{ |
828 | +namespace test |
829 | +{ |
830 | +namespace helpers |
831 | +{ |
832 | +static const std::string FAKE_QUERY {"FAKE_QUERY"}; |
833 | +static const std::string FAKE_CATEGORY_TEMPLATE {"{}"}; |
834 | + |
835 | + |
836 | +class MockIndex : public click::Index { |
837 | + click::Packages packages; |
838 | + click::Packages recommends; |
839 | + click::DepartmentList departments; |
840 | + click::DepartmentList bootstrap_departments; |
841 | + click::HighlightList bootstrap_highlights; |
842 | +public: |
843 | + MockIndex(click::Packages packages = click::Packages(), |
844 | + click::DepartmentList departments = click::DepartmentList(), |
845 | + click::DepartmentList boot_departments = click::DepartmentList()) |
846 | + : Index(QSharedPointer<click::web::Client>()), |
847 | + packages(packages), |
848 | + departments(departments), |
849 | + bootstrap_departments(boot_departments) |
850 | + { |
851 | + |
852 | + } |
853 | + |
854 | + click::web::Cancellable search(const std::string &query, std::function<void (click::Packages, click::Packages)> callback) override |
855 | + { |
856 | + do_search(query, callback); |
857 | + callback(packages, recommends); |
858 | + return click::web::Cancellable(); |
859 | + } |
860 | + |
861 | + click::web::Cancellable bootstrap(std::function<void(const click::DepartmentList&, const click::HighlightList&, Error, int)> callback) override |
862 | + { |
863 | + callback(bootstrap_departments, bootstrap_highlights, click::Index::Error::NoError, 0); |
864 | + return click::web::Cancellable(); |
865 | + } |
866 | + |
867 | + MOCK_METHOD2(do_search, |
868 | + void(const std::string&, |
869 | + std::function<void(click::Packages, click::Packages)>)); |
870 | +}; |
871 | + |
872 | + |
873 | +class FakeCategory : public scopes::Category |
874 | +{ |
875 | +public: |
876 | + FakeCategory(std::string const& id, std::string const& title, |
877 | + std::string const& icon, scopes::CategoryRenderer const& renderer) : |
878 | + scopes::Category(id, title, icon, renderer) |
879 | + { |
880 | + } |
881 | + |
882 | +}; |
883 | +} // namespace helpers |
884 | +} // namespace test |
885 | +} // namespace click |
886 | + |
887 | +#endif // TEST_HELPERS_H |
888 | |
889 | === modified file 'scope/tests/test_query.cpp' |
890 | --- scope/tests/test_query.cpp 2014-06-24 17:18:53 +0000 |
891 | +++ scope/tests/test_query.cpp 2014-07-08 07:23:41 +0000 |
892 | @@ -35,8 +35,8 @@ |
893 | |
894 | #include "click/qtbridge.h" |
895 | #include "clickstore/store-query.h" |
896 | -#include "click/index.h" |
897 | #include "click/application.h" |
898 | +#include "test_helpers.h" |
899 | |
900 | #include <tests/mock_network_access_manager.h> |
901 | |
902 | @@ -49,51 +49,10 @@ |
903 | |
904 | using namespace ::testing; |
905 | using namespace click; |
906 | +using namespace click::test::helpers; |
907 | |
908 | namespace |
909 | { |
910 | -static const std::string FAKE_QUERY {"FAKE_QUERY"}; |
911 | -static const std::string FAKE_CATEGORY_TEMPLATE {"{}"}; |
912 | - |
913 | - |
914 | -class MockIndex : public click::Index { |
915 | - click::Packages packages; |
916 | - click::Packages recommends; |
917 | - click::DepartmentList departments; |
918 | - click::DepartmentList bootstrap_departments; |
919 | - click::HighlightList bootstrap_highlights; |
920 | - |
921 | -public: |
922 | - MockIndex(click::Packages packages = click::Packages(), |
923 | - click::DepartmentList departments = click::DepartmentList(), |
924 | - click::DepartmentList boot_departments = click::DepartmentList()) |
925 | - : Index(QSharedPointer<click::web::Client>()), |
926 | - packages(packages), |
927 | - departments(departments), |
928 | - bootstrap_departments(boot_departments) |
929 | - { |
930 | - |
931 | - } |
932 | - |
933 | - click::web::Cancellable search(const std::string &query, std::function<void (click::Packages, click::Packages)> callback) override |
934 | - { |
935 | - do_search(query, callback); |
936 | - callback(packages, recommends); |
937 | - return click::web::Cancellable(); |
938 | - } |
939 | - |
940 | - |
941 | - click::web::Cancellable bootstrap(std::function<void(const click::DepartmentList&, const click::HighlightList&, Error, int)> callback) override |
942 | - { |
943 | - callback(bootstrap_departments, bootstrap_highlights, click::Index::Error::NoError, 0); |
944 | - return click::web::Cancellable(); |
945 | - } |
946 | - |
947 | - MOCK_METHOD2(do_search, |
948 | - void(const std::string&, |
949 | - std::function<void(click::Packages, click::Packages)>)); |
950 | -}; |
951 | - |
952 | class MockQueryBase : public click::Query { |
953 | public: |
954 | MockQueryBase(const unity::scopes::CannedQuery& query, click::Index& index, |
955 | @@ -140,7 +99,7 @@ |
956 | public: |
957 | MockQueryRun(const unity::scopes::CannedQuery& query, click::Index& index, |
958 | click::DepartmentLookup& depts, |
959 | - click::HighlightList& highlights, |
960 | + click::HighlightList& highlights, |
961 | scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, depts, highlights, metadata) |
962 | { |
963 | |
964 | @@ -154,17 +113,6 @@ |
965 | std::string& categoryTemplate)); |
966 | MOCK_METHOD0(get_installed_packages, PackageSet()); |
967 | }; |
968 | - |
969 | -class FakeCategory : public scopes::Category |
970 | -{ |
971 | -public: |
972 | - FakeCategory(std::string const& id, std::string const& title, |
973 | - std::string const& icon, scopes::CategoryRenderer const& renderer) : |
974 | - scopes::Category(id, title, icon, renderer) |
975 | - { |
976 | - } |
977 | - |
978 | -}; |
979 | } // namespace |
980 | |
981 | TEST(QueryTest, testAddAvailableAppsCallsClickIndex) |
FAILED: Continuous integration, rev:319 jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- ci/166/ jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- utopic- amd64-ci/ 141/console jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- utopic- armhf-ci/ 140/console jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- utopic- i386-ci/ 140/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- team-unity- scope-click- devel-ci/ 166/rebuild
http://