Merge lp:~canonical-platform-qa/autopilot/fix-cpo-having-different-name-1337004 into lp:autopilot

Proposed by Christopher Lee
Status: Merged
Approved by: Christopher Lee
Approved revision: 564
Merged at revision: 567
Proposed branch: lp:~canonical-platform-qa/autopilot/fix-cpo-having-different-name-1337004
Merge into: lp:autopilot
Diff against target: 168 lines (+110/-2)
4 files modified
autopilot/introspection/dbus.py (+38/-1)
autopilot/tests/functional/test_input_stack.py (+1/-1)
autopilot/tests/functional/test_introspection_features.py (+29/-0)
autopilot/tests/unit/test_introspection_dbus.py (+42/-0)
To merge this branch: bzr merge lp:~canonical-platform-qa/autopilot/fix-cpo-having-different-name-1337004
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Max Brustkern (community) Approve
Review via email: mp+262047@code.launchpad.net

This proposal supersedes a proposal from 2015-05-21.

Commit message

Allow Custom Proxy Object classes to have a different name to that of the underlying UI Type

Description of the change

Allow Custom Proxy Object classes to have a different name to that of the underlying UI Type (I.e. having a RedRect CPO for a QQuickRectangle).

To post a comment you must log in.
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote : Posted in a previous version of this proposal

Hi,

You asked for my feedback, so.... brace yourself :D

First, I think this will solve your immediate problem, so don't take anything else I write here as an immediate disapproval. However, I think you're painting yourself into a corner API-design-wise, and it can be avoided.

Let's consider the 60k ft view:

* Test authors want to select one or more objects from the object tree.
* From a data flow POV, that looks like:

 test query (in python)
   -> xpathselect query string (over dbus)
   -> libxpathselect
   -> query data structure (over dbus)
   -> proxy objects (in python)

Currently we give test authors a way to filter what proxy objects get created from the query data structure (via validate_dbus_object), and the long-standing bug you're trying to solve is that the API for generating the query is very limiting on the client-side.

Your solution below is to allow test authors to override the class name generated in the source query.

I think that doesn't go far enough.

Since autopilot 1.5 was released, we have a pretty good encapsulation of the xpathselect query protocol. Why not make the tweaks necessary so that test authors can:

 - create any valid xpathselect query object
 - filter the results as they come back
 - tell autopilot to create proxies with any given class or class mixin

The big, fundamental mistake we made in autopilot was to conflate "a connection to an application" with "a set of classes that can be used to represent application internals". I think that separating those wouldn't be too hard.

The specific issue I see with this merge proposal is, it'll only be a matter of time before Leo (sorry Leo, I'm totally picking on you here) says "I want to write a test where I only select QQuickRectangles that have property X set to Z, and filtering them on the client side is too costly". That's a valid use case, and the only way you'll be able to support that is by further extending an already bloated API.

I suggest you think about how you can separate the concerns mentioned above, and start writing a new query API, while leaving the old API in place for a release to give people time to migrate (hell, you can probably make it 99% backwards compatible).

Let me know if anything doesn't make sense. I'm not subscribed to this MP, so if you reply, you'll need to ping me on IRC...

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
563. By Christopher Lee

Pyflakes fix

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Max Brustkern (nuclearbob) wrote :

Looks reasonable to me.

review: Approve
564. By Christopher Lee

Merge trunk updates

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

 balloons: sorry was out, hey so what if an inelegant solution where I can land the CPO any name change to 1.5 and then you could: if sdk.ver == a: return "MainView" else: return "MainVi12"
This is for now, purely a workaround and not a fix
That would be this MP and I could get that released ASAP as it's already reviewed and tested etc. it'll just be merging to 1.5 and not 1.6 https://code.launchpad.net/~canonical-platform-qa/autopilot/fix-cpo-having-different-name-1337004/+merge/262047

veebers, This would be better than it is, so yes, it would help!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'autopilot/introspection/dbus.py'
2--- autopilot/introspection/dbus.py 2014-07-31 04:42:08 +0000
3+++ autopilot/introspection/dbus.py 2015-07-20 22:38:37 +0000
4@@ -544,6 +544,36 @@
5 class_name = cls.__name__.encode('utf-8')
6 return state_name == class_name
7
8+ @classmethod
9+ def get_type_query_name(cls):
10+ """Return the Type node name to use within the search query.
11+
12+ This allows for a Custom Proxy Object to be named differently to the
13+ underlying node type name.
14+
15+ For instance if you have a QML type defined in the file RedRect.qml::
16+
17+ import QtQuick 2.0
18+ Rectangle {
19+ color: red;
20+ }
21+
22+ You can then define a Custom Proxy Object for this type like so::
23+
24+ class RedRect(DBusIntrospectionObject):
25+ @classmethod
26+ def get_type_query_name(cls):
27+ return 'QQuickRectangle'
28+
29+ This is due to the qml engine storing 'RedRect' as a QQuickRectangle in
30+ the UI tree and the xpathquery query needs a node type to query for.
31+ By default the query will use the class name (in this case RedRect) but
32+ this will not match any node type in the tree.
33+
34+ """
35+
36+ return cls.__name__
37+
38
39 # TODO - can we add a deprecation warning around this somehow?
40 CustomEmulatorBase = DBusIntrospectionObject
41@@ -557,5 +587,12 @@
42
43 """
44 if not isinstance(maybe_string_or_class, str):
45- return maybe_string_or_class.__name__
46+ return _get_class_type_name(maybe_string_or_class)
47 return maybe_string_or_class
48+
49+
50+def _get_class_type_name(maybe_cpo_class):
51+ if hasattr(maybe_cpo_class, 'get_type_query_name'):
52+ return maybe_cpo_class.get_type_query_name()
53+ else:
54+ return maybe_cpo_class.__name__
55
56=== modified file 'autopilot/tests/functional/test_input_stack.py'
57--- autopilot/tests/functional/test_input_stack.py 2015-07-15 21:18:42 +0000
58+++ autopilot/tests/functional/test_input_stack.py 2015-07-20 22:38:37 +0000
59@@ -379,7 +379,7 @@
60 screen_geometry = Display.create().get_screen_geometry(0)
61 target_x = screen_geometry[0] + 10
62 target_y = screen_geometry[1] + 10.6
63- self.device.move(target_x, target_y)
64+ device.move(target_x, target_y)
65 self.assertEqual(device.position(), (target_x, int(target_y)))
66
67 @patch('autopilot.platform.model', new=lambda *args: "Not Desktop", )
68
69=== modified file 'autopilot/tests/functional/test_introspection_features.py'
70--- autopilot/tests/functional/test_introspection_features.py 2015-04-30 03:34:13 +0000
71+++ autopilot/tests/functional/test_introspection_features.py 2015-07-20 22:38:37 +0000
72@@ -42,6 +42,7 @@
73 from autopilot import platform
74 from autopilot.matchers import Eventually
75 from autopilot.testcase import AutopilotTestCase
76+from autopilot.tests.functional import QmlScriptRunnerMixin
77 from autopilot.tests.functional.fixtures import TempDesktopFile
78 from autopilot.introspection import CustomEmulatorBase
79 from autopilot.introspection import _object_registry as object_registry
80@@ -397,3 +398,31 @@
81 [e[1] for e in result2.decorated.errors]
82 )
83 )
84+
85+
86+class CustomCPOTest(AutopilotTestCase, QmlScriptRunnerMixin):
87+
88+ def launch_simple_qml_script(self):
89+ simple_script = dedent("""
90+ import QtQuick 2.0
91+ Rectangle {
92+ objectName: "ExampleRectangle"
93+ }
94+ """)
95+ return self.start_qml_script(simple_script)
96+
97+ def test_cpo_can_be_named_different_to_underlying_type(self):
98+ """A CPO with the correct name match method must be matched if the
99+ class name is different to the Type name.
100+
101+ """
102+ with object_registry.patch_registry({}):
103+ class RandomNamedCPORectangle(CustomEmulatorBase):
104+ @classmethod
105+ def get_type_query_name(cls):
106+ return 'QQuickRectangle'
107+
108+ app = self.launch_simple_qml_script()
109+ rectangle = app.select_single(RandomNamedCPORectangle)
110+
111+ self.assertThat(rectangle.objectName, Equals('ExampleRectangle'))
112
113=== modified file 'autopilot/tests/unit/test_introspection_dbus.py'
114--- autopilot/tests/unit/test_introspection_dbus.py 2014-07-22 02:30:19 +0000
115+++ autopilot/tests/unit/test_introspection_dbus.py 2015-07-20 22:38:37 +0000
116@@ -118,6 +118,28 @@
117 ),
118 )
119
120+ def test_base_class_provides_correct_query_name(self):
121+ self.assertThat(
122+ dbus.DBusIntrospectionObject.get_type_query_name(),
123+ Equals('ProxyBase')
124+ )
125+
126+ def test_inherited_uses_default_get_node_name(self):
127+ class TestCPO(dbus.DBusIntrospectionObject):
128+ pass
129+
130+ self.assertThat(
131+ TestCPO.get_type_query_name(),
132+ Equals('TestCPO')
133+ )
134+
135+ def test_inherited_overwrites_node_name_is_correct(self):
136+ class TestCPO(dbus.DBusIntrospectionObject):
137+ @classmethod
138+ def get_type_query_name(cls):
139+ return "TestCPO"
140+ self.assertThat(TestCPO.get_type_query_name(), Equals("TestCPO"))
141+
142
143 class ProxyObjectPrintTreeTests(TestCase):
144
145@@ -222,3 +244,23 @@
146 class FooBarBaz(object):
147 pass
148 self.assertEqual("FooBarBaz", dbus.get_type_name(FooBarBaz))
149+
150+ def test_get_type_name_returns_classname(self):
151+ class CustomCPO(dbus.DBusIntrospectionObject):
152+ pass
153+
154+ type_name = dbus.get_type_name(CustomEmulatorBase)
155+ self.assertThat(type_name, Equals('ProxyBase'))
156+
157+ def test_get_type_name_returns_custom_node_name(self):
158+ class CustomCPO(dbus.DBusIntrospectionObject):
159+ @classmethod
160+ def get_type_query_name(cls):
161+ return 'TestingCPO'
162+ type_name = dbus.get_type_name(CustomCPO)
163+ self.assertThat(type_name, Equals('TestingCPO'))
164+
165+ def test_get_type_name_returns_classname_of_non_proxybase_classes(self):
166+ class Foo(object):
167+ pass
168+ self.assertEqual('Foo', dbus.get_type_name(Foo))

Subscribers

People subscribed via source and target branches