Merge lp:~mardy/unity-scopes-api/clientid-1554040 into lp:unity-scopes-api
- clientid-1554040
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~mardy/unity-scopes-api/clientid-1554040 |
Merge into: | lp:unity-scopes-api |
Diff against target: |
327 lines (+133/-12) 8 files modified
debian/VERSION (+1/-1) debian/control.in (+1/-0) doc/tutorial.dox (+5/-0) include/unity/scopes/OnlineAccountClient.h (+16/-0) include/unity/scopes/internal/OnlineAccountClientImpl.h (+3/-0) src/scopes/OnlineAccountClient.cpp (+10/-1) src/scopes/internal/OnlineAccountClientImpl.cpp (+26/-2) test/gtest/scopes/OnlineAccountClient/OnlineAccountClient_test.cpp (+71/-8) |
To merge this branch: | bzr merge lp:~mardy/unity-scopes-api/clientid-1554040 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Marcus Tomlinson (community) | Approve | ||
Review via email:
|
This proposal has been superseded by a proposal from 2016-03-16.
Commit message
Allow clients to specify authentication parameters
Description of the change
Allow clients to specify authentication parameters
Please advise on how to set the versioning so that other packages can depend on these changes.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Marcus Tomlinson (marcustomlinson) wrote : | # |
Sorry that this MP has not yet been reviewed, our team is swamped at the moment with OTA10 work. Please bare with us.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Marcus Tomlinson (marcustomlinson) wrote : | # |
Thanks for the contribution Mardy! Please could you address the following:
1) Please update:
1.0.3
to:
1.0.4
in:
/debian/VERSION
2) 3 inline comments
- 361. By Alberto Mardegan
-
Tests, version and docs
- 362. By Alberto Mardegan
-
Clear access token when service is disabled
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:361
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: 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:362
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Marcus Tomlinson (marcustomlinson) wrote : | # |
Thanks for the changes to version and the docs, looks good. Unfortunately, it looks like OnlineAccountCl
58: Test command: /tmp/buildd/
58: Test timeout computed to be: 1500
58: DBus daemon: unix:abstract=
58: DBusMock: Started with PID: 13382
58: /usr/bin/python3: No module named dbusmock
58: DBusMock: Exited with status 256
58:
58: (process:13375): GLib-CRITICAL **: Source ID 6 was not found when attempting to remove it
58:
58: (process:13375): libdbustest-
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Marcus Tomlinson (marcustomlinson) wrote : | # |
As we discussed yesterday, if we could just get coverage of your new logic simply by constructing the main OnlineAccountClient object with a few dummy auth_params (an int, a string, a bool, a double, and one unsupported type), this branch should be good to go.
The changes are small enough that my only real concern here is the memory management.
- 363. By Alberto Mardegan
-
Add missing dependency
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:363
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 364. By Alberto Mardegan
-
Add tests for double and unsupported types
- 365. By Alberto Mardegan
-
Don't put null elements in auth parameters
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:364
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Marcus Tomlinson (marcustomlinson) wrote : | # |
One last small fix (inline)
- 366. By Alberto Mardegan
-
remove endl
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:365
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 367. By Alberto Mardegan
-
merge devel
[ Pawel Stolowski ]
* New RangeInputFilter.
[ Michi Henning ]
* Changed ABI compliance testing to use abigail.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:366
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Unmerged revisions
Preview Diff
1 | === modified file 'debian/VERSION' |
2 | --- debian/VERSION 2016-02-09 10:26:20 +0000 |
3 | +++ debian/VERSION 2016-03-16 12:08:30 +0000 |
4 | @@ -1,1 +1,1 @@ |
5 | -1.0.3 |
6 | +1.0.4 |
7 | |
8 | === modified file 'debian/control.in' |
9 | --- debian/control.in 2015-11-13 04:05:00 +0000 |
10 | +++ debian/control.in 2016-03-16 12:08:30 +0000 |
11 | @@ -32,6 +32,7 @@ |
12 | lttng-tools, |
13 | pkg-config, |
14 | python3:any, |
15 | + python3-dbusmock, |
16 | python-tornado, |
17 | valgrind, |
18 | Standards-Version: 3.9.6 |
19 | |
20 | === modified file 'doc/tutorial.dox' |
21 | --- doc/tutorial.dox 2015-10-29 02:35:50 +0000 |
22 | +++ doc/tutorial.dox 2016-03-16 12:08:30 +0000 |
23 | @@ -1120,6 +1120,11 @@ |
24 | specify our account service name, service type, and provider name (These correspond to the values of the "service_id", |
25 | "type", and "provider" entries in your .service file). |
26 | |
27 | +The constructor accepts a fourth optional parameter which can be used to specify a dictionary of authentication data, |
28 | +whose contents will complement (or override) those authentication parameters specified in the `<template>` element of |
29 | +the \link oa_service `.service` file\endlink. It can be used in those rare cases where the authentication parameters are |
30 | +known only at runtime. |
31 | + |
32 | Via this object we can get the statuses of all account services, set a callback for status updates, and register |
33 | results and widgets that require authorization (See: unity::scopes::OnlineAccountClient class documentation for more |
34 | detail). |
35 | |
36 | === modified file 'include/unity/scopes/OnlineAccountClient.h' |
37 | --- include/unity/scopes/OnlineAccountClient.h 2015-01-19 12:15:58 +0000 |
38 | +++ include/unity/scopes/OnlineAccountClient.h 2016-03-16 12:08:30 +0000 |
39 | @@ -94,6 +94,22 @@ |
40 | std::string const& provider_name, |
41 | MainLoopSelect main_loop_select = CreateInternalMainLoop); |
42 | |
43 | + /** |
44 | + \brief Create OnlineAccountClient for the specified account service. |
45 | + \param service_name The name of the service (E.g. "com.ubuntu.scopes.youtube_youtube"). |
46 | + \param service_type The type of service (E.g. "sharing"). |
47 | + \param provider_name The name of the service provider (E.g. "google"). |
48 | + \param auth_params Authentication parameters; this can be used to pass the |
49 | + OAuth client keys, for example. |
50 | + \param main_loop_select Indicates whether or not an external main loop exists |
51 | + (see OnlineAccountClient::MainLoopSelect). |
52 | + */ |
53 | + OnlineAccountClient(std::string const& service_name, |
54 | + std::string const& service_type, |
55 | + std::string const& provider_name, |
56 | + VariantMap const& auth_params, |
57 | + MainLoopSelect main_loop_select = CreateInternalMainLoop); |
58 | + |
59 | /// @cond |
60 | ~OnlineAccountClient(); |
61 | /// @endcond |
62 | |
63 | === modified file 'include/unity/scopes/internal/OnlineAccountClientImpl.h' |
64 | --- include/unity/scopes/internal/OnlineAccountClientImpl.h 2014-11-03 05:31:30 +0000 |
65 | +++ include/unity/scopes/internal/OnlineAccountClientImpl.h 2016-03-16 12:08:30 +0000 |
66 | @@ -59,6 +59,7 @@ |
67 | OnlineAccountClientImpl(std::string const& service_name, |
68 | std::string const& service_type, |
69 | std::string const& provider_name, |
70 | + VariantMap const& auth_params, |
71 | OnlineAccountClient::MainLoopSelect main_loop_select); |
72 | ~OnlineAccountClientImpl(); |
73 | |
74 | @@ -87,6 +88,7 @@ |
75 | |
76 | std::shared_ptr<AgManager> manager(); |
77 | std::string service_name(); |
78 | + VariantMap const& auth_params() const { return auth_params_; } |
79 | std::shared_ptr<GMainContext> main_loop_context(); |
80 | |
81 | void callback(AccountInfo* info, std::string const& error = ""); |
82 | @@ -99,6 +101,7 @@ |
83 | std::string const service_name_; |
84 | std::string const service_type_; |
85 | std::string const provider_name_; |
86 | + VariantMap const auth_params_; |
87 | OnlineAccountClient::MainLoopSelect const main_loop_select_; |
88 | |
89 | std::mutex callback_mutex_; |
90 | |
91 | === modified file 'src/scopes/OnlineAccountClient.cpp' |
92 | --- src/scopes/OnlineAccountClient.cpp 2014-09-12 04:42:19 +0000 |
93 | +++ src/scopes/OnlineAccountClient.cpp 2016-03-16 12:08:30 +0000 |
94 | @@ -29,7 +29,16 @@ |
95 | std::string const& service_type, |
96 | std::string const& provider_name, |
97 | MainLoopSelect main_loop_select) |
98 | - : p(new internal::OnlineAccountClientImpl(service_name, service_type, provider_name, main_loop_select)) |
99 | + : p(new internal::OnlineAccountClientImpl(service_name, service_type, provider_name, VariantMap(), main_loop_select)) |
100 | +{ |
101 | +} |
102 | + |
103 | +OnlineAccountClient::OnlineAccountClient(std::string const& service_name, |
104 | + std::string const& service_type, |
105 | + std::string const& provider_name, |
106 | + VariantMap const& auth_params, |
107 | + MainLoopSelect main_loop_select) |
108 | + : p(new internal::OnlineAccountClientImpl(service_name, service_type, provider_name, auth_params, main_loop_select)) |
109 | { |
110 | } |
111 | |
112 | |
113 | === modified file 'src/scopes/internal/OnlineAccountClientImpl.cpp' |
114 | --- src/scopes/internal/OnlineAccountClientImpl.cpp 2015-11-25 08:55:19 +0000 |
115 | +++ src/scopes/internal/OnlineAccountClientImpl.cpp 2016-03-16 12:08:30 +0000 |
116 | @@ -87,8 +87,8 @@ |
117 | service_status.service_authenticated = access_token ? info->service_enabled : false; |
118 | service_status.client_id = client_id ? client_id : ""; |
119 | service_status.client_secret = client_secret ? client_secret : ""; |
120 | - service_status.access_token = access_token ? access_token : ""; |
121 | - service_status.token_secret = token_secret ? token_secret : ""; |
122 | + service_status.access_token = (info->service_enabled && access_token) ? access_token : ""; |
123 | + service_status.token_secret = (info->service_enabled && token_secret) ? token_secret : ""; |
124 | service_status.error = error; |
125 | |
126 | return service_status; |
127 | @@ -131,6 +131,19 @@ |
128 | info->account_client->callback(info, error ? error->message : ""); |
129 | } |
130 | |
131 | +static GVariant *variant_to_gvariant(const Variant &v) |
132 | +{ |
133 | + switch (v.which()) { |
134 | + case Variant::Int: return g_variant_new_int32(v.get_int()); |
135 | + case Variant::Bool: return g_variant_new_boolean(v.get_bool()); |
136 | + case Variant::String: return g_variant_new_string(v.get_string().c_str()); |
137 | + case Variant::Double: return g_variant_new_double(v.get_double()); |
138 | + default: |
139 | + std::cerr << "variant_to_gvariant(): unsupported type " << v.serialize_json(); |
140 | + return nullptr; |
141 | + } |
142 | +} |
143 | + |
144 | static void service_update_cb(AgAccountService* account_service, gboolean enabled, AccountInfo* info) |
145 | { |
146 | std::unique_lock<std::mutex> info_lock(info->mutex); |
147 | @@ -179,6 +192,13 @@ |
148 | g_variant_new_int32(SIGNON_POLICY_NO_USER_INTERACTION)); |
149 | } |
150 | |
151 | + for (const auto ¶m: info->account_client->auth_params()) |
152 | + { |
153 | + GVariant* value = variant_to_gvariant(param.second); |
154 | + if (!value) continue; |
155 | + g_variant_builder_add(&builder, "{sv}", param.first.c_str(), value); |
156 | + } |
157 | + |
158 | info->auth_params.reset( |
159 | g_variant_ref_sink(ag_auth_data_get_login_parameters(auth_data.get(), g_variant_builder_end(&builder))), free_variant); |
160 | |
161 | @@ -249,10 +269,12 @@ |
162 | OnlineAccountClientImpl::OnlineAccountClientImpl(std::string const& service_name, |
163 | std::string const& service_type, |
164 | std::string const& provider_name, |
165 | + VariantMap const& auth_params, |
166 | OnlineAccountClient::MainLoopSelect main_loop_select) |
167 | : service_name_(service_name) |
168 | , service_type_(service_type) |
169 | , provider_name_(provider_name) |
170 | + , auth_params_(auth_params) |
171 | , main_loop_select_(main_loop_select) |
172 | , main_loop_is_running_(main_loop_select != OnlineAccountClient::CreateInternalMainLoop) |
173 | { |
174 | @@ -458,6 +480,7 @@ |
175 | account_details_map["service_name"] = service_name_; |
176 | account_details_map["service_type"] = service_type_; |
177 | account_details_map["provider_name"] = provider_name_; |
178 | + account_details_map["auth_params"] = auth_params_; |
179 | account_details_map["login_passed_action"] = static_cast<int>(login_passed_action); |
180 | account_details_map["login_failed_action"] = static_cast<int>(login_failed_action); |
181 | |
182 | @@ -473,6 +496,7 @@ |
183 | account_details_map["service_name"] = service_name_; |
184 | account_details_map["service_type"] = service_type_; |
185 | account_details_map["provider_name"] = provider_name_; |
186 | + account_details_map["auth_params"] = auth_params_; |
187 | account_details_map["login_passed_action"] = static_cast<int>(login_passed_action); |
188 | account_details_map["login_failed_action"] = static_cast<int>(login_failed_action); |
189 | |
190 | |
191 | === modified file 'test/gtest/scopes/OnlineAccountClient/OnlineAccountClient_test.cpp' |
192 | --- test/gtest/scopes/OnlineAccountClient/OnlineAccountClient_test.cpp 2015-04-15 10:05:42 +0000 |
193 | +++ test/gtest/scopes/OnlineAccountClient/OnlineAccountClient_test.cpp 2016-03-16 12:08:30 +0000 |
194 | @@ -58,7 +58,13 @@ |
195 | setenv("AG_SERVICE_TYPES", TEST_DATA_DIR, false); |
196 | setenv("AG_PROVIDERS", TEST_DATA_DIR, false); |
197 | |
198 | - oa_client_.reset(new OnlineAccountClient("TestService", "sharing", "TestProvider", main_loop_select)); |
199 | + VariantMap params; |
200 | + params["ClientId"] = "abcXYZ"; |
201 | + params["Timeout"] = 200; |
202 | + params["UseSSL"] = true; |
203 | + params["Humidity"] = 0.3; |
204 | + params["Unsupported"] = VariantMap(); |
205 | + oa_client_.reset(new OnlineAccountClient("TestService", "sharing", "TestProvider", params, main_loop_select)); |
206 | |
207 | manager_ = oa_client_->p->manager(); |
208 | main_loop_context_ = oa_client_->p->main_loop_context(); |
209 | @@ -295,13 +301,61 @@ |
210 | : OnlineAccountClientTest(OnlineAccountClient::RunInExternalMainLoop) {} |
211 | }; |
212 | |
213 | +class SignondMock |
214 | +{ |
215 | +public: |
216 | + SignondMock() |
217 | + : mock_(dbus_test_dbus_mock_new("com.google.code.AccountsSSO.SingleSignOn")) |
218 | + { |
219 | + DbusTestDbusMockObject* authService = |
220 | + dbus_test_dbus_mock_get_object(mock_, |
221 | + "/com/google/code/AccountsSSO/SingleSignOn", |
222 | + "com.google.code.AccountsSSO.SingleSignOn.AuthService", |
223 | + NULL); |
224 | + |
225 | + dbus_test_dbus_mock_object_add_method(mock_, authService, |
226 | + "getAuthSessionObjectPath", |
227 | + G_VARIANT_TYPE("(us)"), |
228 | + G_VARIANT_TYPE("s"), |
229 | + "ret = '/AuthSession'", |
230 | + NULL); |
231 | + |
232 | + DbusTestDbusMockObject* authSession = |
233 | + dbus_test_dbus_mock_get_object(mock_, |
234 | + "/AuthSession", |
235 | + "com.google.code.AccountsSSO.SingleSignOn.AuthSession", |
236 | + NULL); |
237 | + |
238 | + static const char* const process_code = |
239 | + "data = dict(args[0])\n" |
240 | + "if 'ClientId' in data:\n" |
241 | + " data['AccessToken'] = data['ClientId'].swapcase()\n" |
242 | + "ret = data\n"; |
243 | + dbus_test_dbus_mock_object_add_method(mock_, authSession, |
244 | + "process", |
245 | + G_VARIANT_TYPE("(a{sv}s)"), |
246 | + G_VARIANT_TYPE("a{sv}"), |
247 | + process_code, |
248 | + NULL); |
249 | + } |
250 | + |
251 | + DbusTestTask* task() const { return DBUS_TEST_TASK(mock_); } |
252 | + |
253 | +private: |
254 | + DbusTestDbusMock* mock_; |
255 | +}; |
256 | + |
257 | } // namespace testing |
258 | } // namespace scopes |
259 | } // namespace unity |
260 | |
261 | TEST_F(OnlineAccountClientTest, register_account_login_result) |
262 | { |
263 | - OnlineAccountClient oa_client("test_service_name", "test_service_type", "test_provider"); |
264 | + VariantMap params; |
265 | + params["ClientId"] = "abc"; |
266 | + params["Timeout"] = 200; |
267 | + params["UseSSL"] = true; |
268 | + OnlineAccountClient oa_client("test_service_name", "test_service_type", "test_provider", params); |
269 | |
270 | CategoryRegistry reg; |
271 | auto cat = reg.register_category("1", "title", "icon", nullptr, CategoryRenderer()); |
272 | @@ -319,6 +373,7 @@ |
273 | EXPECT_NE(details.end(), details.find("service_name")); |
274 | EXPECT_NE(details.end(), details.find("service_type")); |
275 | EXPECT_NE(details.end(), details.find("provider_name")); |
276 | + EXPECT_NE(details.end(), details.find("auth_params")); |
277 | EXPECT_NE(details.end(), details.find("login_passed_action")); |
278 | EXPECT_NE(details.end(), details.find("login_failed_action")); |
279 | |
280 | @@ -326,6 +381,10 @@ |
281 | EXPECT_EQ("test_service_name", details.at("service_name").get_string()); |
282 | EXPECT_EQ("test_service_type", details.at("service_type").get_string()); |
283 | EXPECT_EQ("test_provider", details.at("provider_name").get_string()); |
284 | + VariantMap actual_params = details.at("auth_params").get_dict(); |
285 | + EXPECT_EQ("abc", actual_params.at("ClientId").get_string()); |
286 | + EXPECT_EQ(200, actual_params.at("Timeout").get_int()); |
287 | + EXPECT_EQ(true, actual_params.at("UseSSL").get_bool()); |
288 | EXPECT_EQ(OnlineAccountClient::InvalidateResults, static_cast<OnlineAccountClient::PostLoginAction>(details.at("login_passed_action").get_int())); |
289 | EXPECT_EQ(OnlineAccountClient::DoNothing, static_cast<OnlineAccountClient::PostLoginAction>(details.at("login_failed_action").get_int())); |
290 | } |
291 | @@ -423,18 +482,18 @@ |
292 | OnlineAccountClient::ServiceStatus enabled_status; |
293 | enabled_status.account_id = 1; |
294 | enabled_status.service_enabled = true; |
295 | - enabled_status.service_authenticated = false; |
296 | - enabled_status.client_id = "69842936499-sdflkbhslufhgrjamwlicefhb.apps.test.com"; |
297 | + enabled_status.service_authenticated = true; |
298 | + enabled_status.client_id = "abcXYZ"; |
299 | enabled_status.client_secret = "lj3i8iorep0w03994jwjef0j"; |
300 | - enabled_status.access_token = ""; |
301 | + enabled_status.access_token = "ABCxyz"; |
302 | enabled_status.token_secret = ""; |
303 | - enabled_status.error = "GDBus.Error:com.google.code.AccountsSSO.SingleSignOn.Error.MethodNotKnown: Authentication method is not known."; |
304 | + enabled_status.error = ""; |
305 | |
306 | OnlineAccountClient::ServiceStatus disabled_status; |
307 | disabled_status.account_id = 1; |
308 | disabled_status.service_enabled = false; |
309 | disabled_status.service_authenticated = false; |
310 | - disabled_status.client_id = "69842936499-sdflkbhslufhgrjamwlicefhb.apps.test.com"; |
311 | + disabled_status.client_id = "abcXYZ"; |
312 | disabled_status.client_secret = "lj3i8iorep0w03994jwjef0j"; |
313 | disabled_status.access_token = ""; |
314 | disabled_status.token_secret = ""; |
315 | @@ -505,7 +564,11 @@ |
316 | { |
317 | std::shared_ptr<DbusTestService> dbus_test_service; |
318 | dbus_test_service.reset(dbus_test_service_new(nullptr), g_object_unref); |
319 | - dbus_test_service_run(dbus_test_service.get()); |
320 | + |
321 | + unity::scopes::testing::SignondMock signondMock; |
322 | + dbus_test_service_add_task(dbus_test_service.get(), signondMock.task()); |
323 | + |
324 | + dbus_test_service_start_tasks(dbus_test_service.get()); |
325 | |
326 | ::testing::InitGoogleTest(&argc, argv); |
327 | return RUN_ALL_TESTS(); |
PASSED: Continuous integration, rev:360 jenkins. qa.ubuntu. com/job/ unity-scopes- api-ci/ 646/ jenkins. qa.ubuntu. com/job/ unity-scopes- api-vivid- amd64-ci/ 154 jenkins. qa.ubuntu. com/job/ unity-scopes- api-vivid- armhf-ci/ 145 jenkins. qa.ubuntu. com/job/ unity-scopes- api-vivid- armhf-ci/ 145/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ unity-scopes- api-vivid- i386-ci/ 146 jenkins. qa.ubuntu. com/job/ unity-scopes- api-xenial- amd64-ci/ 6 jenkins. qa.ubuntu. com/job/ unity-scopes- api-xenial- armhf-ci/ 6 jenkins. qa.ubuntu. com/job/ unity-scopes- api-xenial- armhf-ci/ 6/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ unity-scopes- api-xenial- i386-ci/ 6
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- scopes- api-ci/ 646/rebuild
http://