Merge lp:~robru/friends/testmode into lp:friends

Proposed by Robert Bruce Park
Status: Merged
Merged at revision: 124
Proposed branch: lp:~robru/friends/testmode
Merge into: lp:friends
Prerequisite: lp:~robru/friends/dbus-invocation
Diff against target: 245 lines (+179/-17)
5 files modified
friends/main.py (+4/-0)
friends/service/mock_service.py (+105/-0)
friends/tests/test_mock_dispatcher.py (+65/-0)
friends/tests/test_model.py (+1/-17)
friends/utils/options.py (+4/-0)
To merge this branch: bzr merge lp:~robru/friends/testmode
Reviewer Review Type Date Requested Status
Ken VanDine Pending
Review via email: mp+146326@code.launchpad.net

Commit message

Reintroduce --test mode, with mock dbus API and test coverage.

Description of the change

Bring back --test mode, this time with a full dbus API that accepts callbacks and the whole deal, acceptable for use as a mock within the libfriends testsuite.

Best part is that I included a test case that ensures the mock has the identical dbus API interface, including identical in/out signatures, as the real Dispatcher... so if in the future somebody changes our dbus API, the testsuite will politely remind them to update the mock, too.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'friends/main.py'
2--- friends/main.py 2013-02-03 21:16:19 +0000
3+++ friends/main.py 2013-02-03 21:16:19 +0000
4@@ -63,6 +63,10 @@
5 print(class_name)
6 return
7
8+ if args.test:
9+ global Dispatcher
10+ from friends.service.mock_service import Dispatcher
11+
12 # Set up the DBus main loop.
13 DBusGMainLoop(set_as_default=True)
14 loop = GLib.MainLoop()
15
16=== added file 'friends/service/mock_service.py'
17--- friends/service/mock_service.py 1970-01-01 00:00:00 +0000
18+++ friends/service/mock_service.py 2013-02-03 21:16:19 +0000
19@@ -0,0 +1,105 @@
20+# friends-service -- send & receive messages from any social network
21+# Copyright (C) 2012 Canonical Ltd
22+#
23+# This program is free software: you can redistribute it and/or modify
24+# it under the terms of the GNU General Public License as published by
25+# the Free Software Foundation, version 3 of the License.
26+#
27+# This program is distributed in the hope that it will be useful,
28+# but WITHOUT ANY WARRANTY; without even the implied warranty of
29+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30+# GNU General Public License for more details.
31+#
32+# You should have received a copy of the GNU General Public License
33+# along with this program. If not, see <http://www.gnu.org/licenses/>.
34+
35+"""DBus service object for general dispatching of commands."""
36+
37+__all__ = [
38+ 'Dispatcher',
39+ ]
40+
41+
42+import json
43+import logging
44+
45+import dbus
46+import dbus.service
47+
48+from friends.service.dispatcher import DBUS_INTERFACE, STUB
49+
50+
51+log = logging.getLogger(__name__)
52+
53+
54+class Dispatcher(dbus.service.Object):
55+ """This object mocks the official friends-service dbus API."""
56+ __dbus_object_path__ = '/com/canonical/friends/Service'
57+
58+ def __init__(self, *ignore):
59+ self.bus = dbus.SessionBus()
60+ bus_name = dbus.service.BusName(DBUS_INTERFACE, bus=self.bus)
61+ super().__init__(bus_name, self.__dbus_object_path__)
62+
63+ self._succeed = True
64+
65+ @dbus.service.method(DBUS_INTERFACE)
66+ def Contacts(self):
67+ pass
68+
69+ @dbus.service.method(DBUS_INTERFACE)
70+ def Refresh(self):
71+ pass
72+
73+ @dbus.service.method(DBUS_INTERFACE)
74+ def ClearIndicators(self):
75+ pass
76+
77+ @dbus.service.method(DBUS_INTERFACE,
78+ in_signature='sss',
79+ out_signature='s',
80+ async_callbacks=('success','failure'))
81+ def Do(self, action, account_id='', arg='',
82+ success=STUB, failure=STUB):
83+ message = "Called with: action={}, account_id={}, arg={}".format(
84+ action, account_id, arg)
85+ success(message) if self._succeed else failure(message)
86+
87+ @dbus.service.method(DBUS_INTERFACE,
88+ in_signature='s',
89+ out_signature='s',
90+ async_callbacks=('success','failure'))
91+ def SendMessage(self, message, success=STUB, failure=STUB):
92+ message = "Called with: message={}".format(message)
93+ success(message) if self._succeed else failure(message)
94+
95+ @dbus.service.method(DBUS_INTERFACE,
96+ in_signature='sss',
97+ out_signature='s',
98+ async_callbacks=('success','failure'))
99+ def SendReply(self, account_id, message_id, msg,
100+ success=STUB, failure=STUB):
101+ message = "Called with: account_id={}, message_id={}, msg={}".format(
102+ account_id, message_id, msg)
103+ success(message) if self._succeed else failure(message)
104+
105+ @dbus.service.method(DBUS_INTERFACE,
106+ in_signature='sss',
107+ out_signature='s',
108+ async_callbacks=('success','failure'))
109+ def Upload(self, account_id, uri, description, success=STUB, failure=STUB):
110+ message = "Called with: account_id={}, uri={}, description={}".format(
111+ account_id, uri, description)
112+ success(message) if self._succeed else failure(message)
113+
114+ @dbus.service.method(DBUS_INTERFACE, in_signature='s', out_signature='s')
115+ def GetFeatures(self, protocol_name):
116+ return json.dumps(protocol_name.split())
117+
118+ @dbus.service.method(DBUS_INTERFACE)
119+ def Quit(self):
120+ self._succeed = False
121+
122+ @dbus.service.method(DBUS_INTERFACE, in_signature='s', out_signature='s')
123+ def URLShorten(self, url):
124+ return str(len(url))
125
126=== added file 'friends/tests/test_mock_dispatcher.py'
127--- friends/tests/test_mock_dispatcher.py 1970-01-01 00:00:00 +0000
128+++ friends/tests/test_mock_dispatcher.py 2013-02-03 21:16:19 +0000
129@@ -0,0 +1,65 @@
130+# friends-service -- send & receive messages from any social network
131+# Copyright (C) 2012 Canonical Ltd
132+#
133+# This program is free software: you can redistribute it and/or modify
134+# it under the terms of the GNU General Public License as published by
135+# the Free Software Foundation, version 3 of the License.
136+#
137+# This program is distributed in the hope that it will be useful,
138+# but WITHOUT ANY WARRANTY; without even the implied warranty of
139+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
140+# GNU General Public License for more details.
141+#
142+# You should have received a copy of the GNU General Public License
143+# along with this program. If not, see <http://www.gnu.org/licenses/>.
144+
145+"""Test the dispatcher directly, without dbus."""
146+
147+__all__ = [
148+ 'TestMockDispatcher',
149+ ]
150+
151+
152+import dbus.service
153+import unittest
154+import json
155+
156+from dbus.mainloop.glib import DBusGMainLoop
157+
158+from friends.service.mock_service import Dispatcher as MockDispatcher
159+from friends.service.dispatcher import Dispatcher
160+from friends.tests.mocks import LogMock, mock
161+
162+
163+# Set up the DBus main loop.
164+DBusGMainLoop(set_as_default=True)
165+
166+
167+def get_signature(klass, signature):
168+ """Extract dbus in/out signatures from a dbus class."""
169+ return [
170+ (m, getattr(getattr(klass, m), signature))
171+ for m in dir(klass)
172+ if hasattr(getattr(klass, m), signature)
173+ ]
174+
175+
176+class TestMockDispatcher(unittest.TestCase):
177+ """Ensure our mock Dispatcher has the same API as the real one."""
178+
179+ def test_api_similarity(self):
180+ real = [m for m in dir(Dispatcher)
181+ if hasattr(getattr(Dispatcher, m), '_dbus_interface')]
182+ mock = [m for m in dir(MockDispatcher)
183+ if hasattr(getattr(MockDispatcher, m), '_dbus_interface')]
184+ self.assertEqual(real, mock)
185+
186+ def test_in_signatures(self):
187+ real = get_signature(Dispatcher, '_dbus_in_signature')
188+ mock = get_signature(MockDispatcher, '_dbus_in_signature')
189+ self.assertEqual(real, mock)
190+
191+ def test_out_signatures(self):
192+ real = get_signature(Dispatcher, '_dbus_out_signature')
193+ mock = get_signature(MockDispatcher, '_dbus_out_signature')
194+ self.assertEqual(real, mock)
195
196=== modified file 'friends/tests/test_model.py'
197--- friends/tests/test_model.py 2012-12-17 22:42:15 +0000
198+++ friends/tests/test_model.py 2013-02-03 21:16:19 +0000
199@@ -26,7 +26,7 @@
200
201 import unittest
202
203-from friends.utils.model import Model, first_run, stale_schema
204+from friends.utils.model import Model
205 from friends.utils.model import prune_model, persist_model
206 from friends.tests.mocks import LogMock, mock
207 from gi.repository import Dee
208@@ -41,22 +41,6 @@
209 def tearDown(self):
210 self.log_mock.stop()
211
212- def test_basic_properties(self):
213- self.assertIsInstance(Model, Dee.SequenceModel)
214- self.assertEqual(Model.get_n_columns(), 18)
215- self.assertEqual(Model.get_schema(),
216- ['aas', 's', 's', 's', 's', 'b', 's', 's', 's', 's',
217- 'd', 'b', 's', 's', 's', 's', 's', 's'])
218- if first_run or stale_schema:
219- # Then the Model should be brand-new and empty
220- self.assertEqual(Model.get_n_rows(), 0)
221-
222- @mock.patch('friends.utils.model._resource_manager')
223- def test_persistence(self, resource_manager):
224- persist_model()
225- resource_manager.store.assert_called_once_with(
226- Model, 'com.canonical.Friends.Streams')
227-
228 @mock.patch('friends.utils.model.Model')
229 @mock.patch('friends.utils.model.persist_model')
230 def test_prune_one(self, persist, model):
231
232=== modified file 'friends/utils/options.py'
233--- friends/utils/options.py 2012-11-16 18:26:56 +0000
234+++ friends/utils/options.py 2013-02-03 21:16:19 +0000
235@@ -32,6 +32,10 @@
236 description='The Friends backend dbus service.')
237
238 self.parser.add_argument(
239+ '-t', '--test',
240+ action='store_true', default=False,
241+ help='Replace friends-service daemon with a crash test dummy.')
242+ self.parser.add_argument(
243 '-d', '--debug',
244 action='store_true', default=False,
245 help='Enable debug level log messages.')

Subscribers

People subscribed via source and target branches