Merge lp:~boiko/dialer-app/rtm-fix_call_hangup_and_test_multi_call into lp:dialer-app/rtm-14.09

Proposed by Gustavo Pichorim Boiko on 2015-04-16
Status: Merged
Approved by: Gustavo Pichorim Boiko on 2015-04-16
Approved revision: 267
Merged at revision: 267
Proposed branch: lp:~boiko/dialer-app/rtm-fix_call_hangup_and_test_multi_call
Merge into: lp:dialer-app/rtm-14.09
Diff against target: 560 lines (+305/-78)
8 files modified
src/qml/LiveCallPage/LiveCall.qml (+3/-2)
src/qml/LiveCallPage/MultiCallDisplay.qml (+3/-0)
tests/autopilot/dialer_app/__init__.py (+26/-1)
tests/autopilot/dialer_app/fixture_setup.py (+83/-0)
tests/autopilot/dialer_app/helpers.py (+39/-15)
tests/autopilot/dialer_app/tests/test_calls.py (+7/-33)
tests/autopilot/dialer_app/tests/test_logs.py (+7/-27)
tests/autopilot/dialer_app/tests/test_multi_calls.py (+137/-0)
To merge this branch: bzr merge lp:~boiko/dialer-app/rtm-fix_call_hangup_and_test_multi_call
Reviewer Review Type Date Requested Status
Gustavo Pichorim Boiko (community) Approve on 2015-04-16
Review via email: mp+256537@code.launchpad.net

Commit message

Make sure the correct call is finished when pressing the hangup button on a multi-call scenario.
Also add autopilot tests to avoid this happening in the future.

Description of the change

Make sure the correct call is finished when pressing the hangup button on a multi-call scenario.
Also add autopilot tests to avoid this happening in the future.

To post a comment you must log in.
Gustavo Pichorim Boiko (boiko) wrote :

Already reviewed for vivid, approving.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/qml/LiveCallPage/LiveCall.qml'
2--- src/qml/LiveCallPage/LiveCall.qml 2015-02-16 20:51:04 +0000
3+++ src/qml/LiveCallPage/LiveCall.qml 2015-04-16 16:26:21 +0000
4@@ -105,7 +105,7 @@
5 } else {
6 closeTimer.running = false;
7 statusLabel.text = "";
8- liveCall.call = callManager.foregroundCall;
9+ liveCall.call = Qt.binding(callManager.foregroundCall);
10 }
11 }
12
13@@ -464,6 +464,7 @@
14
15 MultiCallDisplay {
16 id: multiCallArea
17+ objectName: "multiCallDisplay"
18 calls: callManager.calls
19 opacity: 0
20 anchors {
21@@ -630,7 +631,7 @@
22 }
23
24 LiveCallKeypadButton {
25- objectName: "pauseStartButton"
26+ objectName: "callHoldButton"
27 iconSource: {
28 if (callManager.backgroundCall) {
29 return "swap"
30
31=== modified file 'src/qml/LiveCallPage/MultiCallDisplay.qml'
32--- src/qml/LiveCallPage/MultiCallDisplay.qml 2015-02-10 18:10:34 +0000
33+++ src/qml/LiveCallPage/MultiCallDisplay.qml 2015-04-16 16:26:21 +0000
34@@ -40,8 +40,11 @@
35
36 Item {
37 id: callDelegate
38+ objectName: "callDelegate"
39 property QtObject callEntry: modelData
40 property bool isLast: index == (multiCallRepeater.count - 1)
41+ property bool active: !callEntry.held
42+ property string phoneNumber: callEntry.phoneNumber
43
44 height: units.gu(10) + conferenceArea.height
45 anchors {
46
47=== modified file 'tests/autopilot/dialer_app/__init__.py'
48--- tests/autopilot/dialer_app/__init__.py 2015-02-11 18:29:21 +0000
49+++ tests/autopilot/dialer_app/__init__.py 2015-04-16 16:26:21 +0000
50@@ -35,6 +35,7 @@
51
52 def _click_button(self, button):
53 """Generic way to click a button"""
54+ self.visible.wait_for(True)
55 button.visible.wait_for(True)
56 self.pointing_device.click_object(button)
57 return button
58@@ -50,11 +51,35 @@
59 """Return the hangup button"""
60 return self.wait_select_single(objectName='hangupButton')
61
62+ def _get_call_hold_button(self):
63+ """Return the call holding button"""
64+ return self.wait_select_single(objectName='callHoldButton')
65+
66+ def _get_swap_calls_button(self):
67+ """Return the swap calls button"""
68+ return self._get_call_hold_button()
69+
70+ def get_multi_call_display(self):
71+ """Return the multi call display panel"""
72+ return self.wait_select_single(objectName='multiCallDisplay')
73+
74+ def get_multi_call_item_for_number(self, number):
75+ """Return the multi call display item for the given number"""
76+ return self.wait_select_single(objectName='callDelegate',
77+ phoneNumber=number)
78+
79 def click_hangup_button(self):
80 """Click and return the hangup page"""
81- self.visible.wait_for(True)
82 return self._click_button(self._get_hangup_button())
83
84+ def click_call_hold_button(self):
85+ """Click the call holding button"""
86+ return self._click_button(self._get_call_hold_button())
87+
88+ def click_swap_calls_button(self):
89+ """Click the swap calls button"""
90+ return self._click_button(self._get_swap_calls_button())
91+
92
93 class PageWithBottomEdge(MainView):
94
95
96=== modified file 'tests/autopilot/dialer_app/fixture_setup.py'
97--- tests/autopilot/dialer_app/fixture_setup.py 2014-08-01 15:25:39 +0000
98+++ tests/autopilot/dialer_app/fixture_setup.py 2015-04-16 16:26:21 +0000
99@@ -19,6 +19,8 @@
100
101 import fixtures
102 import subprocess
103+import os
104+import shutil
105
106
107 class TestabilityEnvironment(fixtures.Fixture):
108@@ -48,3 +50,84 @@
109 'QT_LOAD_TESTABILITY'
110 ]
111 )
112+
113+
114+class FillCustomHistory(fixtures.Fixture):
115+
116+ history_db = "history.sqlite"
117+ data_sys = "/usr/lib/python3/dist-packages/dialer_app/data/"
118+ data_local = "dialer_app/data/"
119+ database_path = '/tmp/' + history_db
120+
121+ prefilled_history_local = os.path.join(data_local, history_db)
122+ prefilled_history_system = os.path.join(data_sys, history_db)
123+
124+ def setUp(self):
125+ super(FillCustomHistory, self).setUp()
126+ self.addCleanup(self._clear_test_data)
127+ self.addCleanup(self._kill_service_to_respawn)
128+ self._clear_test_data()
129+ self._prepare_history_data()
130+ self._kill_service_to_respawn()
131+ self._start_service_with_custom_data()
132+
133+ def _prepare_history_data(self):
134+ if os.path.exists(self.prefilled_history_local):
135+ shutil.copy(self.prefilled_history_local, self.database_path)
136+ else:
137+ shutil.copy(self.prefilled_history_system, self.database_path)
138+
139+ def _clear_test_data(self):
140+ if os.path.exists(self.database_path):
141+ os.remove(self.database_path)
142+
143+ def _kill_service_to_respawn(self):
144+ subprocess.call(['pkill', 'history-daemon'])
145+
146+ def _start_service_with_custom_data(self):
147+ os.environ['HISTORY_SQLITE_DBPATH'] = self.database_path
148+ with open(os.devnull, 'w') as devnull:
149+ subprocess.Popen(['history-daemon'], stderr=devnull)
150+
151+
152+class UseEmptyHistory(FillCustomHistory):
153+ database_path = ':memory:'
154+
155+ def setUp(self):
156+ super(UseEmptyHistory, self).setUp()
157+
158+ def _prepare_history_data(self):
159+ # just avoid doing anything
160+ self.database_path = ':memory:'
161+
162+ def _clear_test_data(self):
163+ # don't do anything
164+ self.database_path = ''
165+
166+
167+class UsePhonesimModem(fixtures.Fixture):
168+
169+ def setUp(self):
170+ super().setUp()
171+
172+ # configure the cleanups
173+ self.addCleanup(self._hangupLeftoverCalls)
174+ self.addCleanup(self._restoreModems)
175+
176+ self._switchToPhonesim()
177+
178+ def _switchToPhonesim(self):
179+ # make sure the modem is running on phonesim
180+ subprocess.call(['mc-tool', 'update', 'ofono/ofono/account0',
181+ 'string:modem-objpath=/phonesim'])
182+ subprocess.call(['mc-tool', 'reconnect', 'ofono/ofono/account0'])
183+
184+ def _hangupLeftoverCalls(self):
185+ # ensure that there are no leftover calls in case of failed tests
186+ subprocess.call(["/usr/share/ofono/scripts/hangup-all", "/phonesim"])
187+
188+ def _restoreModems(self):
189+ # set the modem objpath in telepathy-ofono to the real modem
190+ subprocess.call(['mc-tool', 'update', 'ofono/ofono/account0',
191+ 'string:modem-objpath=/ril_0'])
192+ subprocess.call(['mc-tool', 'reconnect', 'ofono/ofono/account0'])
193
194=== modified file 'tests/autopilot/dialer_app/helpers.py'
195--- tests/autopilot/dialer_app/helpers.py 2014-06-20 13:26:49 +0000
196+++ tests/autopilot/dialer_app/helpers.py 2015-04-16 16:26:21 +0000
197@@ -1,8 +1,8 @@
198 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
199 #
200-# Copyright 2014 Canonical Ltd.
201-# Author: Omer Akram <omer.akram@canonical.com>
202-#
203+# Copyright 2014-2015 Canonical Ltd.
204+# Authors: Omer Akram <omer.akram@canonical.com>
205+# Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
206 # This program is free software; you can redistribute it and/or modify
207 # it under the terms of the GNU General Public License version 3, as published
208 # by the Free Software Foundation.
209@@ -21,6 +21,9 @@
210 import sys
211 import time
212 import dbus
213+import tempfile
214+import os
215+import shutil
216
217
218 def wait_for_incoming_call():
219@@ -32,7 +35,7 @@
220 ['/usr/share/ofono/scripts/list-calls'],
221 stderr=subprocess.PIPE,
222 universal_newlines=True)
223- if 'State = incoming' in out:
224+ if 'State = incoming' in out or 'State = waiting' in out:
225 break
226 timeout -= 1
227 time.sleep(0.5)
228@@ -44,17 +47,38 @@
229 subprocess.call(['pkill', '-f', 'notify-osd'])
230
231
232-def invoke_incoming_call():
233- """Invoke an incoming call for test purpose."""
234- # magic number 199 will cause a callback from 1234567; dialing 199
235- # itself will fail, so quiesce the error
236- bus = dbus.SystemBus()
237- vcm = dbus.Interface(bus.get_object('org.ofono', '/phonesim'),
238- 'org.ofono.VoiceCallManager')
239- try:
240- vcm.Dial('199', 'default')
241- except dbus.DBusException:
242- pass
243+def invoke_incoming_call(caller):
244+ """Receive an incoming call from the given caller
245+
246+ :parameter caller: the phone number calling
247+ """
248+
249+ # prepare and send a Qt GUI script to phonesim, over its private D-BUS
250+ # set up by ofono-phonesim-autostart
251+ script_dir = tempfile.mkdtemp(prefix="phonesim_script")
252+ os.chmod(script_dir, 0o755)
253+ with open(os.path.join(script_dir, "call.js"), "w") as f:
254+ f.write("""tabCall.gbIncomingCall.leCaller.text = "%s";
255+tabCall.gbIncomingCall.pbIncomingCall.click();
256+""" % (caller))
257+
258+ with open("/run/lock/ofono-phonesim-dbus.address") as f:
259+ phonesim_bus = f.read().strip()
260+ bus = dbus.bus.BusConnection(phonesim_bus)
261+ script_proxy = bus.get_object("org.ofono.phonesim", "/")
262+ script_proxy.SetPath(script_dir)
263+ script_proxy.Run("call.js")
264+ shutil.rmtree(script_dir)
265+
266+
267+def accept_incoming_call():
268+ """Accept an existing incoming call"""
269+ subprocess.check_call(
270+ [
271+ "dbus-send", "--session", "--print-reply",
272+ "--dest=com.canonical.Approver", "/com/canonical/Approver",
273+ "com.canonical.TelephonyServiceApprover.AcceptCall"
274+ ], stdout=subprocess.PIPE)
275
276
277 def get_phonesim():
278
279=== modified file 'tests/autopilot/dialer_app/tests/test_calls.py'
280--- tests/autopilot/dialer_app/tests/test_calls.py 2015-02-16 20:51:04 +0000
281+++ tests/autopilot/dialer_app/tests/test_calls.py 2015-04-16 16:26:21 +0000
282@@ -10,7 +10,6 @@
283
284 """Tests for the Dialer App using ofono-phonesim"""
285
286-import subprocess
287 import os
288 import time
289
290@@ -20,6 +19,7 @@
291
292 from dialer_app.tests import DialerAppTestCase
293 from dialer_app import helpers
294+from dialer_app import fixture_setup
295
296
297 @skipUnless(helpers.is_phonesim_running(),
298@@ -30,36 +30,15 @@
299 """Tests for simulated phone calls."""
300
301 def setUp(self):
302- # provide clean history
303- self.history = os.path.expanduser(
304- "~/.local/share/history-service/history.sqlite")
305- if os.path.exists(self.history):
306- subprocess.call(["pkill", "history-daemon"])
307- os.rename(self.history, self.history + ".orig")
308-
309- # make sure the modem is running on phonesim
310- subprocess.call(['mc-tool', 'update', 'ofono/ofono/account0',
311- 'string:modem-objpath=/phonesim'])
312- subprocess.call(['mc-tool', 'reconnect', 'ofono/ofono/account0'])
313-
314+ empty_history = fixture_setup.UseEmptyHistory()
315+ self.useFixture(empty_history)
316+ phonesim_modem = fixture_setup.UsePhonesimModem()
317+ self.useFixture(phonesim_modem)
318 super().setUp()
319
320 def tearDown(self):
321 super().tearDown()
322
323- # ensure that there are no leftover calls in case of failed tests
324- subprocess.call(["/usr/share/ofono/scripts/hangup-all", "/phonesim"])
325-
326- # restore history
327- if os.path.exists(self.history + ".orig"):
328- subprocess.call(["pkill", "history-daemon"])
329- os.rename(self.history + ".orig", self.history)
330-
331- # set the modem objpath in telepathy-ofono to the real modem
332- subprocess.call(['mc-tool', 'update', 'ofono/ofono/account0',
333- 'string:modem-objpath=/ril_0'])
334- subprocess.call(['mc-tool', 'reconnect', 'ofono/ofono/account0'])
335-
336 @property
337 def history_list(self):
338 # because of the object tree, more than just one item is returned, but
339@@ -143,19 +122,14 @@
340 def test_incoming(self):
341 """Incoming call"""
342 number = "1234567"
343- helpers.invoke_incoming_call()
344+ helpers.invoke_incoming_call(number)
345
346 # wait for incoming call, accept; it would be nicer to fake-click the
347 # popup notification, but as this isn't generated by dialer-app it
348 # isn't exposed to autopilot
349 helpers.wait_for_incoming_call()
350 time.sleep(1) # let's hear the ringing sound for a second :-)
351- subprocess.check_call(
352- [
353- "dbus-send", "--session", "--print-reply",
354- "--dest=com.canonical.Approver", "/com/canonical/Approver",
355- "com.canonical.TelephonyServiceApprover.AcceptCall"
356- ], stdout=subprocess.PIPE)
357+ helpers.accept_incoming_call()
358
359 # call back is from that number
360 self.assertThat(
361
362=== modified file 'tests/autopilot/dialer_app/tests/test_logs.py'
363--- tests/autopilot/dialer_app/tests/test_logs.py 2015-02-13 17:54:03 +0000
364+++ tests/autopilot/dialer_app/tests/test_logs.py 2015-04-16 16:26:21 +0000
365@@ -10,10 +10,6 @@
366
367 """Tests for the Dialer App"""
368
369-import os
370-import shutil
371-import subprocess
372-
373 from autopilot.platform import model
374 from autopilot.matchers import Eventually
375 from testtools import skipIf
376@@ -33,35 +29,19 @@
377 class TestCallLogs(DialerAppTestCase):
378 """Tests for the call log panel."""
379
380- db_file = 'history.sqlite'
381- local_db_dir = 'dialer_app/data/'
382- system_db_dir = '/usr/lib/python3/dist-packages/dialer_app/data/'
383- temp_db_file = '/tmp/' + db_file
384-
385 def setUp(self):
386- if os.path.exists('../../src/dialer-app'):
387- database = self.local_db_dir + self.db_file
388- else:
389- database = self.system_db_dir + self.db_file
390-
391- if os.path.exists(self.temp_db_file):
392- os.remove(self.temp_db_file)
393-
394- shutil.copyfile(database, self.temp_db_file)
395-
396- subprocess.call(['pkill', 'history-daemon'])
397- os.environ['HISTORY_SQLITE_DBPATH'] = self.temp_db_file
398- with open(os.devnull, 'w') as devnull:
399- subprocess.Popen(['history-daemon'], stderr=devnull)
400-
401- super().setUp()
402+ # set the fixtures before launching the app
403 testability_environment = fixture_setup.TestabilityEnvironment()
404 self.useFixture(testability_environment)
405- self.main_view.dialer_page.reveal_bottom_edge_page()
406- self.addCleanup(subprocess.call, ['pkill', '-f', 'history-daemon'])
407+ fill_history = fixture_setup.FillCustomHistory()
408+ self.useFixture(fill_history)
409 self.fake_url_dispatcher = url_dispatcher_fixtures.FakeURLDispatcher()
410 self.useFixture(self.fake_url_dispatcher)
411
412+ # now launch the app
413+ super().setUp()
414+ self.main_view.dialer_page.reveal_bottom_edge_page()
415+
416 def _get_main_view(self, proxy_object):
417 return proxy_object.wait_select_single('QQuickView')
418
419
420=== added file 'tests/autopilot/dialer_app/tests/test_multi_calls.py'
421--- tests/autopilot/dialer_app/tests/test_multi_calls.py 1970-01-01 00:00:00 +0000
422+++ tests/autopilot/dialer_app/tests/test_multi_calls.py 2015-04-16 16:26:21 +0000
423@@ -0,0 +1,137 @@
424+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
425+# Copyright 2015 Canonical
426+# Author: Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
427+#
428+# This file is part of dialer-app.
429+#
430+# dialer-app is free software: you can redistribute it and/or modify it
431+# under the terms of the GNU General Public License version 3, as published
432+# by the Free Software Foundation.
433+
434+"""Multiple calls tests for the Dialer App using ofono-phonesim"""
435+
436+import time
437+
438+from autopilot.matchers import Eventually
439+from testtools.matchers import Equals
440+from testtools import skipUnless
441+
442+from dialer_app.tests import DialerAppTestCase
443+from dialer_app import helpers
444+from dialer_app import fixture_setup
445+
446+
447+@skipUnless(helpers.is_phonesim_running(),
448+ "this test needs to run under with-ofono-phonesim")
449+class TestMultiCalls(DialerAppTestCase):
450+ """Tests for simulated phone calls."""
451+
452+ def setUp(self):
453+ empty_history = fixture_setup.UseEmptyHistory()
454+ self.useFixture(empty_history)
455+ phonesim_modem = fixture_setup.UsePhonesimModem()
456+ self.useFixture(phonesim_modem)
457+ super().setUp()
458+
459+ def tearDown(self):
460+ super().tearDown()
461+
462+ @property
463+ def history_list(self):
464+ # because of the object tree, more than just one item is returned, but
465+ # all references point to the same item, so take the first
466+ return self.app.select_many(objectName="historyList")[0]
467+
468+ def get_history_for_number(self, number):
469+ # because of the bottom edge tree structure, multiple copies of the
470+ # same item are returned, so just use the first one
471+ return self.history_list.select_many(
472+ "HistoryDelegate", phoneNumber=number)[0]
473+
474+ def place_calls(self, numbers):
475+ for number in numbers:
476+ helpers.invoke_incoming_call(number)
477+ helpers.wait_for_incoming_call()
478+ time.sleep(1)
479+ helpers.accept_incoming_call()
480+ time.sleep(1)
481+
482+ def test_multi_call_panel(self):
483+ """Make sure the multi call panel is visible when two calls are
484+ available"""
485+ firstNumber = '11111111'
486+ secondNumber = '22222222'
487+
488+ # place the calls
489+ self.place_calls([firstNumber, secondNumber])
490+
491+ # now ensure that the multi-call panel is visible
492+ multi_call = self.main_view.live_call_page.get_multi_call_display()
493+ self.assertThat(multi_call.visible, Eventually(Equals(True)))
494+
495+ # hangup one call
496+ self.main_view.live_call_page.click_hangup_button()
497+ self.assertThat(multi_call.visible, Eventually(Equals(False)))
498+
499+ # and the other one
500+ self.main_view.live_call_page.click_hangup_button()
501+
502+ def test_swap_calls(self):
503+ """Check that pressing the swap calls button change the call status"""
504+ firstNumber = '11111111'
505+ secondNumber = '22222222'
506+
507+ # place the calls
508+ self.place_calls([firstNumber, secondNumber])
509+
510+ live_call = self.main_view.live_call_page
511+ firstCallItem = live_call.get_multi_call_item_for_number(firstNumber)
512+ secondCallItem = live_call.get_multi_call_item_for_number(secondNumber)
513+
514+ # check the current status
515+ self.assertThat(firstCallItem.active, Eventually(Equals(False)))
516+ self.assertThat(secondCallItem.active, Eventually(Equals(True)))
517+
518+ # now swap the calls
519+ live_call.click_swap_calls_button()
520+
521+ # and make sure the calls changed
522+ self.assertThat(firstCallItem.active, Eventually(Equals(True)))
523+ self.assertThat(secondCallItem.active, Eventually(Equals(False)))
524+
525+ # hangup the calls
526+ self.main_view.live_call_page.click_hangup_button()
527+ time.sleep(1)
528+ self.main_view.live_call_page.click_hangup_button()
529+
530+ def test_swap_and_hangup(self):
531+ """Check that after swapping calls and hanging up the correct call
532+ stays active"""
533+ firstNumber = '11111111'
534+ secondNumber = '22222222'
535+
536+ # place the calls
537+ self.place_calls([firstNumber, secondNumber])
538+
539+ # at this point the calls should be like this:
540+ # - 11111111: held
541+ # - 22222222: active
542+ # swap the calls then
543+ self.main_view.live_call_page.click_swap_calls_button()
544+
545+ # - 11111111: active
546+ # - 22222222: held
547+ self.assertThat(
548+ self.main_view.live_call_page.caller,
549+ Eventually(Equals(firstNumber)))
550+
551+ # hangup the active call
552+ self.main_view.live_call_page.click_hangup_button()
553+
554+ # and check that the remaining call is the one that was held
555+ self.assertThat(
556+ self.main_view.live_call_page.caller,
557+ Eventually(Equals(secondNumber)))
558+
559+ # and hangup the remaining call too
560+ self.main_view.live_call_page.click_hangup_button()

Subscribers

People subscribed via source and target branches