Merge lp:~boiko/dialer-app/fix_call_hangup_and_test_multi_calls into lp:dialer-app

Proposed by Gustavo Pichorim Boiko
Status: Merged
Approved by: Tiago Salem Herrmann
Approved revision: 398
Merged at revision: 395
Proposed branch: lp:~boiko/dialer-app/fix_call_hangup_and_test_multi_calls
Merge into: lp:dialer-app
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/fix_call_hangup_and_test_multi_calls
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Tiago Salem Herrmann (community) Approve
Review via email: mp+256319@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.

== Checklist ==
Are there any related MPs required for this MP to build/function as expected? Please list.
No

Is your branch in sync with latest trunk (e.g. bzr pull lp:trunk -> no changes)
Yes

Did you perform an exploratory manual test run of your code change and any related functionality on device or emulator?
Yes

Did you successfully run all tests found in your component's Test Plan (https://wiki.ubuntu.com/Process/Merges/TestPlan/<package-name>) on device or emulator?
Yes

If you changed the UI, was the change specified/approved by design?
N/A

If you changed UI labels, did you update the pot file?
N/A

If you changed the packaging (debian), did you add a core-dev as a reviewer to this MP?
N/A

To post a comment you must log in.
398. By Gustavo Pichorim Boiko

Make sure the fixtures are set before launching the app.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
399. By Gustavo Pichorim Boiko

Fix header.

Revision history for this message
Tiago Salem Herrmann (tiagosh) wrote :

Did you perform an exploratory manual test run of the code change and any related functionality on device or emulator?
Yes

Did CI run pass? If not, please explain why.
No, but not related to the changes

Have you checked that submitter has accurately filled out the submitter checklist and has taken no shortcut?
yes

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

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-03-30 14:04:11 +0000
3+++ src/qml/LiveCallPage/LiveCall.qml 2015-04-15 18:25:37 +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@@ -465,6 +465,7 @@
14
15 MultiCallDisplay {
16 id: multiCallArea
17+ objectName: "multiCallDisplay"
18 calls: callManager.calls
19 opacity: 0
20 anchors {
21@@ -631,7 +632,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-03-20 13:43:16 +0000
33+++ src/qml/LiveCallPage/MultiCallDisplay.qml 2015-04-15 18:25:37 +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 17:28:08 +0000
49+++ tests/autopilot/dialer_app/__init__.py 2015-04-15 18:25:37 +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-15 18:25:37 +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-15 18:25:37 +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 18:24:27 +0000
281+++ tests/autopilot/dialer_app/tests/test_calls.py 2015-04-15 18:25:37 +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:46:39 +0000
364+++ tests/autopilot/dialer_app/tests/test_logs.py 2015-04-15 18:25:37 +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-15 18:25:37 +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