Merge lp:~autopilot/autopilot-gtk/experimental into lp:autopilot-gtk
- experimental
- Merge into trunk
Status: | Merged | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Martin Pitt | ||||||||||||||||||||
Approved revision: | 68 | ||||||||||||||||||||
Merged at revision: | 53 | ||||||||||||||||||||
Proposed branch: | lp:~autopilot/autopilot-gtk/experimental | ||||||||||||||||||||
Merge into: | lp:autopilot-gtk | ||||||||||||||||||||
Diff against target: |
957 lines (+425/-133) 13 files modified
debian/changelog (+7/-0) debian/control (+4/-4) debian/rules (+2/-1) lib/GtkNode.cpp (+147/-60) lib/GtkNode.h (+18/-8) lib/GtkRootNode.cpp (+24/-16) lib/GtkRootNode.h (+5/-3) lib/Introspection.cpp (+3/-3) lib/Variant.cpp (+168/-22) lib/Variant.h (+3/-1) lib/autopilot_types.h (+34/-0) tests/autopilot/tests/test_widget_tree.py (+2/-6) tests/autopilot/tests/test_xpath_query.py (+8/-9) |
||||||||||||||||||||
To merge this branch: | bzr merge lp:~autopilot/autopilot-gtk/experimental | ||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Martin Pitt (community) | Approve | ||
Review via email: mp+185845@code.launchpad.net |
Commit message
Version 1.4 wire protocol changes.
Description of the change
Version 1.4 protocol changes.
PS Jenkins bot (ps-jenkins) wrote : | # |
Martin Pitt (pitti) wrote : | # |
This mostly looks okay visually, except that this needs to grow a
Breaks: autopilot (<< 1.4)
to avoid trying to install an 1.4 version of libautopilot-gtk with an 1.3 version of autopilot.
Also, actually approving this branch needs to wait for the PS jenkins tests to succeed, which requires landing xpathselect first.
Martin Pitt (pitti) wrote : | # |
Err, of course not "Breaks: autpilot", but "Breaks: python-autopilot (<< 1.4), python3-autopilot (<< 1.4)"
The latter is not strictly necessary as we did'nt yet have p3-ap for 1.3, but it'll provide a nice template for bumping to 1.5 in the future.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:65
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Francis Ginther (fginther) wrote : | # |
Autopilot is now in the local archive, re-approve.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
Thomi Richards (thomir-deactivatedaccount) wrote : | # |
Above i386 failure was xvfb failing to start, trying again, since it seems like an intermittent failure.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
Martin Pitt (pitti) wrote : | # |
The requested Breaks: got added, so changing my vote to approved.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:68
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'debian/changelog' |
2 | --- debian/changelog 2013-06-28 00:01:10 +0000 |
3 | +++ debian/changelog 2013-09-17 13:46:39 +0000 |
4 | @@ -1,3 +1,10 @@ |
5 | +autopilot-gtk (1.4-0ubuntu1) saucy; urgency=low |
6 | + |
7 | + [ Thomi Richards ] |
8 | + * Version bump, updatedx to new xpathselect API. |
9 | + |
10 | + -- Thomi Richards <thomi.richards@canonical.com> Thu, 15 Aug 2013 15:12:39 +1200 |
11 | + |
12 | autopilot-gtk (1.3+13.10.20130628-0ubuntu1) saucy; urgency=low |
13 | |
14 | [ Martin Pitt ] |
15 | |
16 | === modified file 'debian/control' |
17 | --- debian/control 2013-06-25 15:35:28 +0000 |
18 | +++ debian/control 2013-09-17 13:46:39 +0000 |
19 | @@ -7,14 +7,13 @@ |
20 | pkg-config, |
21 | libglib2.0-dev, |
22 | libgtk-3-dev, |
23 | - libxpathselect-dev (>= 1.3), |
24 | + libxpathselect-dev (>= 1.4), |
25 | xvfb, |
26 | dbus-x11, |
27 | - python-autopilot, |
28 | - python-xlib, |
29 | + autopilot-desktop, |
30 | + bamfdaemon, |
31 | python-evdev, |
32 | python-gi, |
33 | - gir1.2-gtk-3.0, |
34 | gsettings-desktop-schemas, |
35 | gnome-system-log, |
36 | Standards-Version: 3.9.4 |
37 | @@ -33,6 +32,7 @@ |
38 | Pre-Depends: ${misc:Pre-Depends}, |
39 | Depends: ${shlibs:Depends}, |
40 | ${misc:Depends}, |
41 | +Breaks: python-autopilot (<< 1.4), python3-autopilot (<< 1.4) |
42 | Replaces: autopilot-gtk, libautopilot-gtk0, libautopilot-gtk-dev |
43 | Conflicts: autopilot-gtk, libautopilot-gtk0, libautopilot-gtk-dev |
44 | Provides: autopilot-gtk, libautopilot-gtk0 |
45 | |
46 | === modified file 'debian/rules' |
47 | --- debian/rules 2013-06-26 06:05:38 +0000 |
48 | +++ debian/rules 2013-09-17 13:46:39 +0000 |
49 | @@ -9,7 +9,8 @@ |
50 | override_dh_auto_test: |
51 | mkdir -p debian/tmp/home/run |
52 | env HOME=$(CURDIR)/debian/tmp/home XDG_RUNTIME_DIR=$(CURDIR)/debian/tmp/home/run \ |
53 | - xvfb-run dh_auto_test |
54 | + xvfb-run -a -e $(CURDIR)/debian/tmp/X.log dh_auto_test || \ |
55 | + { echo "==== X.org log ===="; cat $(CURDIR)/debian/tmp/X.log; false; } |
56 | |
57 | override_dh_install: |
58 | dh_install --fail-missing |
59 | |
60 | === modified file 'lib/GtkNode.cpp' |
61 | --- lib/GtkNode.cpp 2013-06-27 17:02:58 +0000 |
62 | +++ lib/GtkNode.cpp 2013-09-17 13:46:39 +0000 |
63 | @@ -25,11 +25,40 @@ |
64 | #include "Variant.h" |
65 | |
66 | const std::string GtkNode::AP_ID_NAME = "id"; |
67 | +static guint32 cur_obj_id = 2; // start at 2 since 1 is reserved for the root node |
68 | |
69 | -GtkNode::GtkNode(GObject* obj, std::string const& parent_path) |
70 | - : object_(obj) { |
71 | +GtkNode::GtkNode(GObject* obj, GtkNode::Ptr const& parent) |
72 | + : object_(obj) |
73 | + , parent_(parent) |
74 | +{ |
75 | + std::string parent_path = parent ? parent->GetPath() : ""; |
76 | full_path_ = parent_path + "/" + GetName(); |
77 | - if (object_ != NULL) g_object_ref(object_); |
78 | + if (object_ != NULL) |
79 | + { |
80 | + g_object_ref(object_); |
81 | + GQuark OBJ_ID = g_quark_from_static_string("AUTOPILOT_OBJECT_ID"); |
82 | + gpointer val = g_object_get_qdata (object_, OBJ_ID); |
83 | + if (val == NULL) |
84 | + { |
85 | + g_object_set_qdata (object_, OBJ_ID, reinterpret_cast<gpointer>(cur_obj_id++)); |
86 | + } |
87 | + } |
88 | +} |
89 | + |
90 | +GtkNode::GtkNode(GObject* obj) |
91 | + : object_(obj) |
92 | +{ |
93 | + full_path_ = "/" + GetName(); |
94 | + if (object_ != NULL) |
95 | + { |
96 | + g_object_ref(object_); |
97 | + GQuark OBJ_ID = g_quark_from_static_string("AUTOPILOT_OBJECT_ID"); |
98 | + gpointer val = g_object_get_qdata (object_, OBJ_ID); |
99 | + if (val == NULL) |
100 | + { |
101 | + g_object_set_qdata (object_, OBJ_ID, reinterpret_cast<gpointer>(cur_obj_id++)); |
102 | + } |
103 | + } |
104 | } |
105 | |
106 | GtkNode::~GtkNode() |
107 | @@ -109,7 +138,7 @@ |
108 | g_value_init(&value, param_spec->value_type); |
109 | g_object_get_property(object_, g_param_spec_get_name(param_spec), &value); |
110 | convert_value(param_spec, &value); |
111 | - builder_wrapper.add(param_spec->name, &value); |
112 | + builder_wrapper.add_gvalue(param_spec->name, &value); |
113 | g_value_unset(&value); //Free the memory accquired by the value object. Absence of this was causig the applications to crash. |
114 | } |
115 | } else { |
116 | @@ -119,7 +148,7 @@ |
117 | g_free(properties); |
118 | |
119 | // add our unique autopilot-id |
120 | - builder_wrapper.add(AP_ID_NAME.c_str(), GetObjectId()); |
121 | + builder_wrapper.add(AP_ID_NAME.c_str(), GetId()); |
122 | |
123 | // add the names of our children |
124 | builder_wrapper.add("Children", GetChildNodeNames()); |
125 | @@ -136,9 +165,7 @@ |
126 | if (GDK_IS_WINDOW(gdk_window)) { |
127 | GdkRectangle rect; |
128 | GetGlobalRect(&rect); |
129 | - //g_debug("Rect coords %d, %d, %d, %d", rect.x, rect.y, rect.width, rect.height); |
130 | - GVariant *rect_gvariant = ComposeRectVariant(rect.x, rect.y, rect.width, rect.height); |
131 | - builder_wrapper.add("globalRect", rect_gvariant); |
132 | + builder_wrapper.add("globalRect", rect); |
133 | } |
134 | } else if (ATK_IS_COMPONENT(object_)) { |
135 | AddAtkComponentProperties(builder_wrapper, ATK_COMPONENT(object_)); |
136 | @@ -146,18 +173,6 @@ |
137 | return g_variant_builder_end(&builder); |
138 | } |
139 | |
140 | -GVariant* GtkNode::ComposeRectVariant(gint x, gint y, gint height, gint width) const |
141 | -{ |
142 | - //g_debug("composing a rect variant"); |
143 | - GVariantBuilder builder; |
144 | - g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); |
145 | - g_variant_builder_add(&builder, "i", x); |
146 | - g_variant_builder_add(&builder, "i", y); |
147 | - g_variant_builder_add(&builder, "i", height); |
148 | - g_variant_builder_add(&builder, "i", width); |
149 | - return g_variant_builder_end(&builder); |
150 | -} |
151 | - |
152 | void GtkNode::AddAtkComponentProperties(variant::BuilderWrapper &builder_wrapper, |
153 | AtkComponent *atk_component) const |
154 | { |
155 | @@ -171,8 +186,12 @@ |
156 | x = y = width = height = -1; |
157 | atk_component_get_extents(atk_component, &x, &y, &width, &height, |
158 | ATK_XY_SCREEN); |
159 | - GVariant *rect_gvariant = ComposeRectVariant(x, y, width, height); |
160 | - builder_wrapper.add("globalRect", rect_gvariant); |
161 | + GdkRectangle r; |
162 | + r.x = x; |
163 | + r.y = y; |
164 | + r.width = width; |
165 | + r.height = height; |
166 | + builder_wrapper.add("globalRect", r); |
167 | } |
168 | |
169 | builder_wrapper.add("active", |
170 | @@ -196,10 +215,6 @@ |
171 | g_object_unref(G_OBJECT(states)); |
172 | } |
173 | |
174 | -intptr_t GtkNode::GetObjectId() const { |
175 | - return reinterpret_cast<intptr_t>(object_); |
176 | -} |
177 | - |
178 | void GtkNode::GetGlobalRect(GdkRectangle* rect) const |
179 | { |
180 | GtkWidget *widget = GTK_WIDGET(object_); |
181 | @@ -233,11 +248,24 @@ |
182 | return full_path_; |
183 | } |
184 | |
185 | -bool GtkNode::MatchProperty(const std::string& name, |
186 | - const std::string& value) const { |
187 | - if (name == "id") |
188 | - return value == std::to_string(GetObjectId()); |
189 | +int32_t GtkNode::GetId() const |
190 | +{ |
191 | + GQuark OBJ_ID = g_quark_from_static_string("AUTOPILOT_OBJECT_ID"); |
192 | + gpointer val = g_object_get_qdata (object_, OBJ_ID); |
193 | + // this uglyness is required in order to stop the compiler complaining about the fact |
194 | + // that we're casting a 64 bit type (gpointer) down to a 32 bit type (gint32) and may |
195 | + // be truncating the value. It's safe to do, however, since we control what values are |
196 | + // set in this quark, and they were initial gint32 values anyway. |
197 | + guint32 id = static_cast<gint32>(reinterpret_cast<intptr_t>(val)); |
198 | + return id; |
199 | +} |
200 | |
201 | +xpathselect::Node::Ptr GtkNode::GetParent() const |
202 | +{ |
203 | + return parent_; |
204 | +} |
205 | +bool GtkNode::MatchStringProperty(const std::string& name, |
206 | + const std::string& value) const { |
207 | if (name == "BuilderName" && GTK_IS_BUILDABLE(object_)) { |
208 | const gchar* name = gtk_buildable_get_name(GTK_BUILDABLE (object_)); |
209 | return name != NULL && std::string(name) == value; |
210 | @@ -258,41 +286,100 @@ |
211 | convert_value(pspec, &dest_value); |
212 | std::string dest_string; |
213 | |
214 | - // convert it to a string; always doing string comparison avoids having to do |
215 | - // type comparison, conversion and error handling on value |
216 | - switch (G_VALUE_TYPE(&dest_value)) { |
217 | - case G_TYPE_INT: |
218 | - dest_string = std::to_string(g_value_get_int(&dest_value)); |
219 | - break; |
220 | - case G_TYPE_UINT: |
221 | - dest_string = std::to_string(g_value_get_uint(&dest_value)); |
222 | - break; |
223 | - case G_TYPE_DOUBLE: |
224 | - dest_string = std::to_string(g_value_get_double(&dest_value)); |
225 | - break; |
226 | - case G_TYPE_STRING: { |
227 | + if (G_VALUE_TYPE(&dest_value) == G_TYPE_STRING) { |
228 | const gchar *str = g_value_get_string(&dest_value); |
229 | dest_string = (str != NULL) ? str : ""; |
230 | - break; |
231 | - } |
232 | - default: |
233 | - g_debug("Unhandled type %s for matching property %s", |
234 | - g_type_name(G_VALUE_TYPE(&dest_value)), g_param_spec_get_name(pspec)); |
235 | - g_value_unset(&dest_value); |
236 | - return false; |
237 | - } |
238 | - |
239 | - g_value_unset(&dest_value); |
240 | - return dest_string == value; |
241 | -} |
242 | - |
243 | -xpathselect::NodeList GtkNode::Children() const { |
244 | + g_value_unset(&dest_value); |
245 | + return dest_string == value; |
246 | + } |
247 | + else { |
248 | + g_debug("Property %s exists, but is not a string (is %s).", |
249 | + g_param_spec_get_name(pspec), |
250 | + g_type_name(G_VALUE_TYPE(&dest_value)) |
251 | + ); |
252 | + g_value_unset(&dest_value); |
253 | + return false; |
254 | + } |
255 | + |
256 | +} |
257 | + |
258 | +bool GtkNode::MatchIntegerProperty(const std::string& name, |
259 | + int32_t value) const { |
260 | + if (name == "id") |
261 | + return value == GetId(); |
262 | + |
263 | + GObjectClass* klass = G_OBJECT_GET_CLASS(object_); |
264 | + GParamSpec* pspec = g_object_class_find_property(klass, name.c_str()); |
265 | + if (pspec == NULL) |
266 | + return false; |
267 | + |
268 | + // read the property into a GValue |
269 | + g_debug("Matching property %s of type (%s).", g_param_spec_get_name(pspec), |
270 | + g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec))); |
271 | + |
272 | + GValue dest_value = G_VALUE_INIT; |
273 | + g_value_init(&dest_value, G_PARAM_SPEC_VALUE_TYPE(pspec)); |
274 | + g_object_get_property(object_, name.c_str(), &dest_value); |
275 | + convert_value(pspec, &dest_value); |
276 | + |
277 | + if (G_VALUE_TYPE(&dest_value) == G_TYPE_INT) { |
278 | + int v = g_value_get_int(&dest_value); |
279 | + g_value_unset(&dest_value); |
280 | + return value == v; |
281 | + } |
282 | + else if (G_VALUE_TYPE(&dest_value) == G_TYPE_UINT) { |
283 | + int v = g_value_get_uint(&dest_value); |
284 | + g_value_unset(&dest_value); |
285 | + return value == v; |
286 | + } |
287 | + else { |
288 | + g_debug("Property %s exists, but is not an integer (is %s).", |
289 | + g_param_spec_get_name(pspec), |
290 | + g_type_name(G_VALUE_TYPE(&dest_value)) |
291 | + ); |
292 | + g_value_unset(&dest_value); |
293 | + return false; |
294 | + } |
295 | +} |
296 | + |
297 | +bool GtkNode::MatchBooleanProperty(const std::string& name, |
298 | + bool value) const { |
299 | + GObjectClass* klass = G_OBJECT_GET_CLASS(object_); |
300 | + GParamSpec* pspec = g_object_class_find_property(klass, name.c_str()); |
301 | + if (pspec == NULL) |
302 | + return false; |
303 | + |
304 | + // read the property into a GValue |
305 | + g_debug("Matching property %s of type (%s).", g_param_spec_get_name(pspec), |
306 | + g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec))); |
307 | + |
308 | + GValue dest_value = G_VALUE_INIT; |
309 | + g_value_init(&dest_value, G_PARAM_SPEC_VALUE_TYPE(pspec)); |
310 | + g_object_get_property(object_, name.c_str(), &dest_value); |
311 | + convert_value(pspec, &dest_value); |
312 | + |
313 | + if (G_VALUE_TYPE(&dest_value) == G_TYPE_BOOLEAN) { |
314 | + bool v = g_value_get_boolean(&dest_value); |
315 | + g_value_unset(&dest_value); |
316 | + return value == v; |
317 | + } |
318 | + else { |
319 | + g_debug("Property %s exists, but is not a boolean (is %s).", |
320 | + g_param_spec_get_name(pspec), |
321 | + g_type_name(G_VALUE_TYPE(&dest_value)) |
322 | + ); |
323 | + g_value_unset(&dest_value); |
324 | + return false; |
325 | + } |
326 | +} |
327 | + |
328 | +xpathselect::NodeVector GtkNode::Children() const { |
329 | //g_debug("getting the children of a node"); |
330 | - xpathselect::NodeList children; |
331 | + xpathselect::NodeVector children; |
332 | if (GTK_IS_CONTAINER(object_)) { |
333 | GList* gtk_children = gtk_container_get_children(GTK_CONTAINER(object_)); |
334 | for (GList* elem = gtk_children; elem; elem = elem->next) { |
335 | - children.push_back(std::make_shared<GtkNode>(G_OBJECT(elem->data), GetPath())); |
336 | + children.push_back(std::make_shared<GtkNode>(G_OBJECT(elem->data), shared_from_this())); |
337 | } |
338 | g_list_free(gtk_children); |
339 | } else if (ATK_IS_OBJECT(object_)) { |
340 | @@ -300,7 +387,7 @@ |
341 | int n_children = atk_object_get_n_accessible_children(atk_object); |
342 | for (int i = 0; i < n_children; i++) { |
343 | AtkObject *child = atk_object_ref_accessible_child(atk_object, i); |
344 | - children.push_back(std::make_shared<GtkNode>(G_OBJECT(child), GetPath())); |
345 | + children.push_back(std::make_shared<GtkNode>(G_OBJECT(child), shared_from_this())); |
346 | } |
347 | } |
348 | |
349 | |
350 | === modified file 'lib/GtkNode.h' |
351 | --- lib/GtkNode.h 2013-04-21 23:04:43 +0000 |
352 | +++ lib/GtkNode.h 2013-09-17 13:46:39 +0000 |
353 | @@ -25,33 +25,43 @@ |
354 | #include <xpathselect/node.h> |
355 | #include <xpathselect/xpathselect.h> |
356 | #include <string> |
357 | +#include <cstdint> |
358 | #include "Variant.h" |
359 | |
360 | -class GtkNode: public xpathselect::Node { |
361 | +// #include <memory> |
362 | + |
363 | +class GtkNode: public xpathselect::Node, public std::enable_shared_from_this<GtkNode> |
364 | +{ |
365 | public: |
366 | - typedef std::shared_ptr<GtkNode> Ptr; |
367 | + typedef std::shared_ptr<const GtkNode> Ptr; |
368 | |
369 | - GtkNode(GObject* object, std::string const& parent_name); |
370 | + GtkNode(GObject* object, Ptr const& parent); |
371 | + GtkNode(GObject* object); |
372 | virtual ~GtkNode(); |
373 | |
374 | virtual GVariant* Introspect() const; |
375 | |
376 | virtual std::string GetName() const; |
377 | virtual std::string GetPath() const; |
378 | - virtual bool MatchProperty(const std::string& name, |
379 | - const std::string& value) const; |
380 | - virtual xpathselect::NodeList Children() const; |
381 | + virtual int32_t GetId() const; |
382 | + virtual xpathselect::Node::Ptr GetParent() const; |
383 | + virtual bool MatchStringProperty(const std::string& name, |
384 | + const std::string& value) const; |
385 | + virtual bool MatchIntegerProperty(const std::string& name, |
386 | + int32_t value) const; |
387 | + virtual bool MatchBooleanProperty(const std::string& name, |
388 | + bool value) const; |
389 | + virtual xpathselect::NodeVector Children() const; |
390 | |
391 | static const std::string AP_ID_NAME; |
392 | |
393 | private: |
394 | GObject *object_; |
395 | std::string full_path_; |
396 | + Ptr parent_; |
397 | |
398 | virtual GVariant* GetChildNodeNames() const; |
399 | - virtual intptr_t GetObjectId() const; |
400 | virtual void GetGlobalRect(GdkRectangle* rect) const; |
401 | - virtual GVariant* ComposeRectVariant(gint x, gint y, gint height, gint width) const; |
402 | void AddAtkComponentProperties(variant::BuilderWrapper &builder_wrapper, |
403 | AtkComponent *atk_component) const; |
404 | }; |
405 | |
406 | === modified file 'lib/GtkRootNode.cpp' |
407 | --- lib/GtkRootNode.cpp 2013-04-21 23:04:43 +0000 |
408 | +++ lib/GtkRootNode.cpp 2013-09-17 13:46:39 +0000 |
409 | @@ -27,7 +27,7 @@ |
410 | #include "Variant.h" |
411 | |
412 | GtkRootNode::GtkRootNode() |
413 | - : GtkNode(NULL, std::string()) { |
414 | + : GtkNode(NULL) { |
415 | } |
416 | |
417 | GVariant* GtkRootNode::Introspect() const { |
418 | @@ -35,14 +35,13 @@ |
419 | g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); |
420 | variant::BuilderWrapper builder_wrapper(&builder); |
421 | // add our unique autopilot-id |
422 | - builder_wrapper.add(AP_ID_NAME.c_str(), GetObjectId()); |
423 | + builder_wrapper.add(AP_ID_NAME.c_str(), GetId()); |
424 | // add the names of our children |
425 | builder_wrapper.add("Children", GetChildNodeNames()); |
426 | return g_variant_builder_end(&builder); |
427 | } |
428 | |
429 | -intptr_t GtkRootNode::GetObjectId() const { |
430 | - // FIXME: we could be consistent and return the memory address |
431 | +int32_t GtkRootNode::GetId() const { |
432 | return 1; |
433 | } |
434 | |
435 | @@ -54,30 +53,39 @@ |
436 | return "/" + GetName(); |
437 | } |
438 | |
439 | -bool GtkRootNode::MatchProperty(const std::string& name, |
440 | - const std::string& value) const { |
441 | - //g_debug("matching a property for the root node"); |
442 | +bool GtkRootNode::MatchIntegerProperty(const std::string& name, int32_t value) const |
443 | +{ |
444 | + // Root node only matches one property - id: |
445 | if (name == "id") |
446 | - // yes we just compare as a string; use of intptr_t makes casting tricky |
447 | - return value == std::to_string(GetObjectId()); |
448 | - return false; |
449 | -} |
450 | - |
451 | -xpathselect::NodeList GtkRootNode::Children() const { |
452 | + return value == GetId(); |
453 | + return false; |
454 | +} |
455 | + |
456 | +bool GtkRootNode::MatchBooleanProperty(const std::string& name, bool value) const |
457 | +{ |
458 | + return false; |
459 | +} |
460 | + |
461 | +bool GtkRootNode::MatchStringProperty(const std::string& name, const std::string& value) const |
462 | +{ |
463 | + return false; |
464 | +} |
465 | + |
466 | +xpathselect::NodeVector GtkRootNode::Children() const { |
467 | //g_debug("getting the children of a node"); |
468 | - xpathselect::NodeList children; |
469 | + xpathselect::NodeVector children; |
470 | |
471 | // add all the toplevel nodes as children to the root node |
472 | GList* toplevels_list = gtk_window_list_toplevels(); |
473 | GList* elem; |
474 | for (elem = toplevels_list; elem; elem = elem->next) { |
475 | GObject *node = reinterpret_cast<GObject*>(elem->data); |
476 | - children.push_back(std::make_shared<GtkNode>(node, GetPath())); |
477 | + children.push_back(std::make_shared<GtkNode>(node, shared_from_this())); |
478 | |
479 | // if the AtkObjects are available, expose the Atk hierarchy as well |
480 | AtkObject *atk_object = gtk_widget_get_accessible(GTK_WIDGET(node)); |
481 | if (atk_object != NULL) |
482 | - children.push_back(std::make_shared<GtkNode>(G_OBJECT(atk_object), GetPath())); |
483 | + children.push_back(std::make_shared<GtkNode>(G_OBJECT(atk_object), shared_from_this())); |
484 | } |
485 | g_list_free(toplevels_list); |
486 | return children; |
487 | |
488 | === modified file 'lib/GtkRootNode.h' |
489 | --- lib/GtkRootNode.h 2013-04-18 05:10:07 +0000 |
490 | +++ lib/GtkRootNode.h 2013-09-17 13:46:39 +0000 |
491 | @@ -33,12 +33,14 @@ |
492 | GtkRootNode(); |
493 | |
494 | virtual GVariant* Introspect() const; |
495 | - virtual intptr_t GetObjectId() const; |
496 | |
497 | virtual std::string GetName() const; |
498 | virtual std::string GetPath() const; |
499 | - virtual bool MatchProperty(const std::string& name, const std::string& value) const; |
500 | - virtual xpathselect::NodeList Children() const; |
501 | + virtual int32_t GetId() const; |
502 | + virtual bool MatchIntegerProperty(const std::string& name, int32_t value) const; |
503 | + virtual bool MatchBooleanProperty(const std::string& name, bool value) const; |
504 | + virtual bool MatchStringProperty(const std::string& name, const std::string& value) const; |
505 | + virtual xpathselect::NodeVector Children() const; |
506 | |
507 | private: |
508 | virtual GVariant* GetChildNodeNames() const; |
509 | |
510 | === modified file 'lib/Introspection.cpp' |
511 | --- lib/Introspection.cpp 2013-05-08 02:52:12 +0000 |
512 | +++ lib/Introspection.cpp 2013-09-17 13:46:39 +0000 |
513 | @@ -82,7 +82,7 @@ |
514 | { |
515 | autopilot_introspection_complete_get_version(introspection_service, |
516 | invocation, |
517 | - "1.3"); |
518 | + "1.4"); |
519 | return TRUE; |
520 | } |
521 | |
522 | @@ -110,12 +110,12 @@ |
523 | |
524 | //g_debug("selecting nodes"); |
525 | std::list<GtkNode::Ptr> node_list; |
526 | - xpathselect::NodeList selected_nodes_list; |
527 | + xpathselect::NodeVector selected_nodes_list; |
528 | selected_nodes_list = xpathselect::SelectNodes(root, query_string); |
529 | //g_debug("finished selecting nodes"); |
530 | for (auto node : selected_nodes_list) { |
531 | // node may be our root node wrapper *or* an ordinary GObject wrapper |
532 | - auto object_ptr = std::static_pointer_cast<GtkNode>(node); |
533 | + auto object_ptr = std::static_pointer_cast<const GtkNode>(node); |
534 | if (object_ptr) |
535 | node_list.push_back(object_ptr); |
536 | } |
537 | |
538 | === modified file 'lib/Variant.cpp' |
539 | --- lib/Variant.cpp 2013-02-13 02:38:30 +0000 |
540 | +++ lib/Variant.cpp 2013-09-17 13:46:39 +0000 |
541 | @@ -17,9 +17,12 @@ |
542 | * Authored by: Tim Penhey <tim.penhey@canonical.com> |
543 | */ |
544 | |
545 | +#include <gdk/gdk.h> |
546 | + |
547 | #include <iostream> |
548 | #include "Variant.h" |
549 | |
550 | +#include "autopilot_types.h" |
551 | namespace variant |
552 | { |
553 | |
554 | @@ -29,82 +32,210 @@ |
555 | |
556 | BuilderWrapper& BuilderWrapper::add(char const* name, bool value) |
557 | { |
558 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_boolean(value)); |
559 | + GVariantBuilder b; |
560 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
561 | + |
562 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
563 | + g_variant_builder_add(&b, "v", g_variant_new_boolean(value)); |
564 | + |
565 | + g_variant_builder_add( |
566 | + builder_, |
567 | + "{sv}", |
568 | + name, |
569 | + g_variant_builder_end(&b) |
570 | + ); |
571 | return *this; |
572 | } |
573 | |
574 | BuilderWrapper& BuilderWrapper::add(char const* name, char const* value) |
575 | { |
576 | + GVariantBuilder b; |
577 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
578 | + |
579 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
580 | if (value) |
581 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_string(value)); |
582 | + g_variant_builder_add(&b, "v", g_variant_new_string(value)); |
583 | else |
584 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_string("")); |
585 | + g_variant_builder_add(&b, "v", g_variant_new_string("")); |
586 | + |
587 | + g_variant_builder_add( |
588 | + builder_, |
589 | + "{sv}", |
590 | + name, |
591 | + g_variant_builder_end(&b) |
592 | + ); |
593 | |
594 | return *this; |
595 | } |
596 | |
597 | BuilderWrapper& BuilderWrapper::add(char const* name, std::string const& value) |
598 | { |
599 | - g_variant_builder_add(builder_, "{sv}", name, |
600 | - g_variant_new_string(value.c_str())); |
601 | + GVariantBuilder b; |
602 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
603 | + |
604 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
605 | + g_variant_builder_add(&b, "v", g_variant_new_string(value.c_str())); |
606 | + |
607 | + g_variant_builder_add( |
608 | + builder_, |
609 | + "{sv}", |
610 | + name, |
611 | + g_variant_builder_end(&b) |
612 | + ); |
613 | return *this; |
614 | } |
615 | |
616 | BuilderWrapper& BuilderWrapper::add(char const* name, int value) |
617 | { |
618 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_int32(value)); |
619 | + GVariantBuilder b; |
620 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
621 | + |
622 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
623 | + g_variant_builder_add(&b, "v", g_variant_new_int32(value)); |
624 | + |
625 | + g_variant_builder_add( |
626 | + builder_, |
627 | + "{sv}", |
628 | + name, |
629 | + g_variant_builder_end(&b) |
630 | + ); |
631 | return *this; |
632 | } |
633 | |
634 | BuilderWrapper& BuilderWrapper::add(char const* name, long int value) |
635 | { |
636 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_int64(value)); |
637 | + GVariantBuilder b; |
638 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
639 | + |
640 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
641 | + g_variant_builder_add(&b, "v", g_variant_new_int64(value)); |
642 | + g_variant_builder_add( |
643 | + builder_, |
644 | + "{sv}", |
645 | + name, |
646 | + g_variant_builder_end(&b) |
647 | + ); |
648 | return *this; |
649 | } |
650 | |
651 | BuilderWrapper& BuilderWrapper::add(char const* name, long long int value) |
652 | { |
653 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_int64(value)); |
654 | + GVariantBuilder b; |
655 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
656 | + |
657 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
658 | + g_variant_builder_add(&b, "v", g_variant_new_int64(value)); |
659 | + g_variant_builder_add( |
660 | + builder_, |
661 | + "{sv}", |
662 | + name, |
663 | + g_variant_builder_end(&b) |
664 | + ); |
665 | return *this; |
666 | } |
667 | |
668 | BuilderWrapper& BuilderWrapper::add(char const* name, unsigned int value) |
669 | { |
670 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_uint32(value)); |
671 | + GVariantBuilder b; |
672 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
673 | + |
674 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
675 | + g_variant_builder_add(&b, "v", g_variant_new_uint32(value)); |
676 | + g_variant_builder_add( |
677 | + builder_, |
678 | + "{sv}", |
679 | + name, |
680 | + g_variant_builder_end(&b) |
681 | + ); |
682 | return *this; |
683 | } |
684 | |
685 | BuilderWrapper& BuilderWrapper::add(char const* name, long unsigned int value) |
686 | { |
687 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_uint64(value)); |
688 | + GVariantBuilder b; |
689 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
690 | + |
691 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
692 | + g_variant_builder_add(&b, "v", g_variant_new_uint64(value)); |
693 | + |
694 | + g_variant_builder_add( |
695 | + builder_, |
696 | + "{sv}", |
697 | + name, |
698 | + g_variant_builder_end(&b) |
699 | + ); |
700 | return *this; |
701 | } |
702 | |
703 | BuilderWrapper& BuilderWrapper::add(char const* name, long long unsigned int value) |
704 | { |
705 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_uint64(value)); |
706 | + GVariantBuilder b; |
707 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
708 | + |
709 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
710 | + g_variant_builder_add(&b, "v", g_variant_new_uint64(value)); |
711 | + g_variant_builder_add( |
712 | + builder_, |
713 | + "{sv}", |
714 | + name, |
715 | + g_variant_builder_end(&b) |
716 | + ); |
717 | return *this; |
718 | } |
719 | |
720 | BuilderWrapper& BuilderWrapper::add(char const* name, float value) |
721 | { |
722 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_double(value)); |
723 | + GVariantBuilder b; |
724 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
725 | + |
726 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
727 | + g_variant_builder_add(&b, "v", g_variant_new_double(value)); |
728 | + g_variant_builder_add( |
729 | + builder_, |
730 | + "{sv}", |
731 | + name, |
732 | + g_variant_builder_end(&b) |
733 | + ); |
734 | return *this; |
735 | } |
736 | |
737 | BuilderWrapper& BuilderWrapper::add(char const* name, double value) |
738 | { |
739 | - g_variant_builder_add(builder_, "{sv}", name, g_variant_new_double(value)); |
740 | + GVariantBuilder b; |
741 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
742 | + |
743 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
744 | + g_variant_builder_add(&b, "v", g_variant_new_double(value)); |
745 | + |
746 | + g_variant_builder_add( |
747 | + builder_, |
748 | + "{sv}", |
749 | + name, |
750 | + g_variant_builder_end(&b) |
751 | + ); |
752 | return *this; |
753 | } |
754 | |
755 | BuilderWrapper& BuilderWrapper::add(char const* name, GVariant* value) |
756 | { |
757 | - g_variant_builder_add(builder_, "{sv}", name, value); |
758 | + if (value) |
759 | + { |
760 | + GVariantBuilder b; |
761 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
762 | + |
763 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_PLAIN)); |
764 | + g_variant_builder_add(&b, "v", value); |
765 | + g_variant_builder_add( |
766 | + builder_, |
767 | + "{sv}", |
768 | + name, |
769 | + g_variant_builder_end(&b) |
770 | + ); |
771 | + } |
772 | return *this; |
773 | } |
774 | |
775 | -BuilderWrapper& BuilderWrapper::add(char const* name, GValue* value) |
776 | +BuilderWrapper& BuilderWrapper::add_gvalue(char const* name, GValue* value) |
777 | { |
778 | switch (G_VALUE_TYPE(value)) { |
779 | case G_TYPE_CHAR: |
780 | @@ -119,7 +250,7 @@ |
781 | break; |
782 | case G_TYPE_BOOLEAN: |
783 | { |
784 | - add(name, g_value_get_boolean(value)); |
785 | + add(name, (bool) g_value_get_boolean(value)); |
786 | } |
787 | break; |
788 | case G_TYPE_INT: |
789 | @@ -197,17 +328,32 @@ |
790 | add(name, g_value_get_object(value)); |
791 | } |
792 | break; |
793 | - case G_TYPE_VARIANT: |
794 | - { |
795 | - add(name, g_value_get_variant(value)); |
796 | - } |
797 | - break; |
798 | default: |
799 | - //g_warning("unsupported type: %s", g_type_name(G_VALUE_TYPE(value))); |
800 | + g_warning("unsupported type: %s", g_type_name(G_VALUE_TYPE(value))); |
801 | {} |
802 | break; |
803 | } |
804 | return *this; |
805 | } |
806 | |
807 | +BuilderWrapper& BuilderWrapper::BuilderWrapper::add(char const* name, GdkRectangle value) |
808 | +{ |
809 | + GVariantBuilder b; |
810 | + g_variant_builder_init(&b, G_VARIANT_TYPE("av")); |
811 | + |
812 | + g_variant_builder_add(&b, "v", g_variant_new_int32(TYPE_RECT)); |
813 | + g_variant_builder_add(&b, "v", g_variant_new_int32(value.x)); |
814 | + g_variant_builder_add(&b, "v", g_variant_new_int32(value.y)); |
815 | + g_variant_builder_add(&b, "v", g_variant_new_int32(value.width)); |
816 | + g_variant_builder_add(&b, "v", g_variant_new_int32(value.height)); |
817 | + |
818 | + g_variant_builder_add( |
819 | + builder_, |
820 | + "{sv}", |
821 | + name, |
822 | + g_variant_builder_end(&b) |
823 | + ); |
824 | + return *this; |
825 | +} |
826 | + |
827 | } |
828 | |
829 | === modified file 'lib/Variant.h' |
830 | --- lib/Variant.h 2013-02-13 02:38:30 +0000 |
831 | +++ lib/Variant.h 2013-09-17 13:46:39 +0000 |
832 | @@ -46,7 +46,9 @@ |
833 | BuilderWrapper& add(char const* name, float value); |
834 | BuilderWrapper& add(char const* name, double value); |
835 | BuilderWrapper& add(char const* name, GVariant* value); |
836 | - BuilderWrapper& add(char const* name, GValue* value); |
837 | + BuilderWrapper& add_gvalue(char const* name, GValue* value); |
838 | + |
839 | + BuilderWrapper& add(char const* name, GdkRectangle value); |
840 | |
841 | private: |
842 | GVariantBuilder* builder_; |
843 | |
844 | === added file 'lib/autopilot_types.h' |
845 | --- lib/autopilot_types.h 1970-01-01 00:00:00 +0000 |
846 | +++ lib/autopilot_types.h 2013-09-17 13:46:39 +0000 |
847 | @@ -0,0 +1,34 @@ |
848 | +/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
849 | + * Copyright (C) 2013 Canonical Ltd |
850 | + * |
851 | + * This program is free software: you can redistribute it and/or modify |
852 | + * it under the terms of the GNU General Public License version 3 as |
853 | + * published by the Free Software Foundation. |
854 | + * |
855 | + * This program is distributed in the hope that it will be useful, |
856 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
857 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
858 | + * GNU General Public License for more details. |
859 | + * |
860 | + * You should have received a copy of the GNU General Public License |
861 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
862 | + * |
863 | + */ |
864 | + |
865 | +#ifndef AUTOPILOT_TYPES_H |
866 | +#define AUTOPILOT_TYPES_H |
867 | + |
868 | +/// IMPORTANT: THese constants are taken from the autopilot XPathSelect protocol document. |
869 | +/// Only add options here if the support has been added for them in autopilot itself. |
870 | +enum autopilot_type_id |
871 | +{ |
872 | + TYPE_PLAIN = 0, |
873 | + TYPE_RECT = 1, |
874 | + TYPE_POINT = 2, |
875 | + TYPE_SIZE = 3, |
876 | + TYPE_COLOR = 4, |
877 | + TYPE_DATETIME = 5, |
878 | + TYPE_TIME = 6, |
879 | +}; |
880 | + |
881 | +#endif |
882 | |
883 | === modified file 'tests/autopilot/tests/test_widget_tree.py' |
884 | --- tests/autopilot/tests/test_widget_tree.py 2013-06-26 09:31:14 +0000 |
885 | +++ tests/autopilot/tests/test_widget_tree.py 2013-09-17 13:46:39 +0000 |
886 | @@ -79,7 +79,7 @@ |
887 | |
888 | # qualified: visible property is not unique |
889 | self.assertRaises(ValueError, |
890 | - self.app.select_single, 'GtkButton', visible=1) |
891 | + self.app.select_single, 'GtkButton', visible=True) |
892 | |
893 | # qualified: label property is unique within GtkButton |
894 | w = self.app.select_single('GtkButton', label='gtk-quit') |
895 | @@ -98,7 +98,7 @@ |
896 | self.assertRaises(ValueError, self.app.select_single, label='gtk-quit') |
897 | |
898 | # ... but it is unique for focussable widgets (menus don't allow that) |
899 | - w = self.app.select_single(label='gtk-quit', can_focus=1) |
900 | + w = self.app.select_single(label='gtk-quit', can_focus=True) |
901 | self.assertIn('.GtkButton', str(type(w))) |
902 | self.assertEqual(w.label, 'gtk-quit') |
903 | |
904 | @@ -141,8 +141,6 @@ |
905 | # button and menu item |
906 | self.assertEqual(len(res), 2) |
907 | |
908 | - # https://launchpad.net/bugs/1194763 |
909 | - @unittest.expectedFailure |
910 | def test_select_int(self): |
911 | """select_*() with int properties""" |
912 | |
913 | @@ -158,8 +156,6 @@ |
914 | |
915 | self.assertNotEqual(self.app.select_single(border_width=2), None) |
916 | |
917 | - # https://launchpad.net/bugs/1194763 |
918 | - @unittest.expectedFailure |
919 | def test_select_bool(self): |
920 | """select_*() with boolean properties""" |
921 | |
922 | |
923 | === modified file 'tests/autopilot/tests/test_xpath_query.py' |
924 | --- tests/autopilot/tests/test_xpath_query.py 2013-06-25 12:58:00 +0000 |
925 | +++ tests/autopilot/tests/test_xpath_query.py 2013-09-17 13:46:39 +0000 |
926 | @@ -83,23 +83,22 @@ |
927 | def test_select_by_attribute(self): |
928 | """Select widgets with attribute pattern""" |
929 | |
930 | - state = self.app.get_state_by_path('//*[label=gtk-delete]') |
931 | + state = self.app.get_state_by_path('//*[label="gtk-delete"]') |
932 | self.assertEqual(len(state), 1, state) |
933 | - self.assertEqual(state[0][1]['label'], 'gtk-delete') |
934 | + self.assertEqual(state[0][1]['label'], [0, 'gtk-delete']) |
935 | self.assertTrue(state[0][0].endswith('/GtkButton'), state[0][0]) |
936 | |
937 | # https://launchpad.net/bugs/1179806 |
938 | + # TODO: Make this pass! |
939 | @unittest.expectedFailure |
940 | def test_select_by_attribute_spaces(self): |
941 | """Select widgets with attribute pattern containing spaces""" |
942 | |
943 | - # none of these work ATM, but are supposed to: |
944 | - #state = self.app.get_state_by_path('//*[label=Hello Color!]') |
945 | - #state = self.app.get_state_by_path('//*[label=Hello Color!]') |
946 | - state = self.app.get_state_by_path('//*[label="Hello Color!"]') |
947 | - self.assertEqual(len(state), 1, str(state)) |
948 | - self.assertEqual(state[0][1]['label'], 'Hello Color!') |
949 | - self.assertTrue(state[0][0].endswith('/GtkLabel'), state[0][0]) |
950 | + for state_str in ('//*[label="Hello\\x20Color!"]', '//*[label="Hello Color!"]'): |
951 | + state = self.app.get_state_by_path(state_str) |
952 | + self.assertEqual(len(state), 1, str(state)) |
953 | + self.assertEqual(state[0][1]['label'], 'Hello Color!') |
954 | + self.assertTrue(state[0][0].endswith('/GtkLabel'), state[0][0]) |
955 | |
956 | @classmethod |
957 | def _get_widgets(klass, obj, widget_set): |
FAILED: Continuous integration, rev:64 jenkins. qa.ubuntu. com/job/ autopilot- gtk-ci/ 61/ jenkins. qa.ubuntu. com/job/ autopilot- gtk-saucy- amd64-ci/ 23/console jenkins. qa.ubuntu. com/job/ autopilot- gtk-saucy- armhf-ci/ 23/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ autopilot- gtk-ci/ 61/rebuild
http://