Merge lp:~mandel/ubuntuone-control-panel/auto-update-looping-call into lp:ubuntuone-control-panel
- auto-update-looping-call
- Merge into trunk
Status: | Rejected |
---|---|
Rejected by: | Manuel de la Peña |
Proposed branch: | lp:~mandel/ubuntuone-control-panel/auto-update-looping-call |
Merge into: | lp:ubuntuone-control-panel |
Prerequisite: | lp:~mandel/ubuntuone-control-panel/auto-update-functions |
Diff against target: |
834 lines (+488/-115) 15 files modified
setup.py (+1/-0) ubuntuone/controlpanel/gui/__init__.py (+2/-0) ubuntuone/controlpanel/gui/qt/gui.py (+13/-0) ubuntuone/controlpanel/gui/qt/main/__init__.py (+1/-1) ubuntuone/controlpanel/gui/qt/systray.py (+17/-0) ubuntuone/controlpanel/gui/qt/task/__init__.py (+30/-0) ubuntuone/controlpanel/gui/qt/task/linux.py (+54/-0) ubuntuone/controlpanel/gui/qt/task/tests/__init__.py (+17/-0) ubuntuone/controlpanel/gui/qt/task/tests/test_linux.py (+82/-0) ubuntuone/controlpanel/gui/qt/tests/test_gui.py (+132/-0) ubuntuone/controlpanel/gui/qt/tests/test_start.py (+0/-108) ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+77/-5) ubuntuone/controlpanel/utils/__init__.py (+4/-0) ubuntuone/controlpanel/utils/tests/test_windows.py (+46/-1) ubuntuone/controlpanel/utils/windows.py (+12/-0) |
To merge this branch: | bzr merge lp:~mandel/ubuntuone-control-panel/auto-update-looping-call |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Natalia Bidart (community) | Needs Fixing | ||
Alejandro J. Cura (community) | Needs Fixing | ||
Review via email: mp+81015@code.launchpad.net |
Commit message
Provides the code so that there is a looping call in the control panel that will check for update of the application.
Description of the change
Provides the code so that there is a looping call in the control panel that will check for update of the application.
Manuel de la Peña (mandel) wrote : | # |
> This style of coding tests is very ugly and error prone:
>
> 534 + def _clean_
> 535 + """Clean the fake window class."""
> 536 + FakeMainWindow.
> 537 + FakeMainWindow.
> 538 +
> 539 + def _clean_
> 540 + """Clean the fake icon class."""
> 541 + FakeTrayIcon.window = None
> 542 + FakeTrayIcon.
> 543 +
> 544 + def _clean_
> 545 + """Clean the fake looping call class."""
> 546 + FakeLoopingCall
> 547 + FakeLoopingCall
> 548 + FakeLoopingCall
> 549 + FakeLoopingCall
>
> Instead of patching with a class that gets its attributes overwritten, please
> patch with an instance that gets thrown away, so there's no need to reset the
> class attributes like in the code above.
>
> For instance:
> "self.patch(gui, 'LoopingCall', FakeLoopingCall)" ->
> "self.patch(gui, 'LoopingCall', FakeLoopingCall
>
> This is present a few times in this branch, so make sure all occurrences end
> up fixed.
>
Fixed them on ubuntuone/
> ---
>
> Also, to test code that uses LoopingCalls (and reactor.callLaters, too)
> remember that you can use twisted.
> LoopingCall.clock attribute. Then you will be able to fake the reactor moving
> forward in time by calling clock.advance.
>
> This gives you a lot of flexibility to test this kind of code, and the tests
> end up a lot faster and reliable than if they were using a small value for the
> LoopingCall or callLater time.
In my tests I'm asserting that the Looping call was indeed instantiated and started and I trust that the twisted implementation will be correct. I don't think I really need to move the clock to assert that the function was indeed called since the branch this branch depends on already tests that function.
I prefer not to force looping calls to be called since I find those types of tests to be integration tests. I'll leave the tests as they are so we can have a conversation later.
- 251. By Manuel de la Peña
-
Merged with parent.
- 252. By Manuel de la Peña
-
Fixed tests.
- 253. By Manuel de la Peña
-
Ensure __call__ returns self.
- 254. By Manuel de la Peña
-
Merged with parent and fixed conflicts.
- 255. By Manuel de la Peña
-
Resolve conflicts.
- 256. By Manuel de la Peña
-
Fixed lint issues.
- 257. By Manuel de la Peña
-
Removed all unecesary tests.
Natalia Bidart (nataliabidart) wrote : | # |
This import code:
from ubuntuone.
AUTOUPDATE_
)
should be:
from ubuntuone.
autoupdate,
AUTOUPDATE_
)
Also, in ubuntuone/
47 - if with_icon or minimized:
48 + if with_icon:
Manuel de la Peña (mandel) wrote : | # |
Looking at the code you will see that we had:
46 if not minimized:
47 - if with_icon or minimized:
so if minimized = False we go through the if at line 46 we have that an x or False == x, right?
- 258. By Manuel de la Peña
-
Merged with trunk.
- 259. By Manuel de la Peña
-
Fix the order of the import.
Alejandro J. Cura (alecu) wrote : | # |
The TrayIcon constructor takes a LoopingCall as a parameter, but it's never used in production code.
It should be either removed or used.
---
Both FakeMainWindow and FakeTrayIcon are modifying the class attributes.
For instance:
FakeMainWind
The tests should be operating on object instances instead of classes, since modifying classes is bad form and causes maintenance issues.
- 260. By Manuel de la Peña
-
Create an assign in the same statement.
- 261. By Manuel de la Peña
-
Reviews changes:Improved the tests not to use the class attrs.
Removed unused __init__ parameter. - 262. By Manuel de la Peña
-
Fixed bug qhen calling the lc.
- 263. By Manuel de la Peña
-
Fixed qt tray tests.
- 264. By Manuel de la Peña
-
Fixed broken test after changes in FakeWindow.
Alejandro J. Cura (alecu) wrote : | # |
Code looks good, all tests pass.
Approving.
- 265. By Manuel de la Peña
-
Merged with trunk and fixed conflicts.
- 266. By Manuel de la Peña
-
Fixed tests so that they work when using the FakeSdTool.
- 267. By Manuel de la Peña
-
Fixed some small lint issues.
Alejandro J. Cura (alecu) wrote : | # |
I've just been warned by nessita that if we land this branch it will mean that the qt control panel on linux will need the qt4reactor, and we are trying to move away from it.
Nessita will add more details to this bug.
Natalia Bidart (nataliabidart) wrote : | # |
* The file ubuntuone/
* Also, like alecu mentioned, we are working on having the QT control panel running only a Qt main loop in Linux, since we need to drop the dependency on the qt4reactor in that OS because we can't file a MIR for it (at least for now). So, since the autoupdate code is only relevant to windows, we'd need to move all that (included the LoopingCall, which brings in the need of a twisted reactor) to a windows-specific location, for example, main/windows.py.
I'd suggest to move the 'start' method to the 'main/' python package, and have all the autoupdate code moved to main/windows.py.
Sorry if I was not explicit about this before, but the goal of dropping the qt4reactor in Linux is a rather new objective.
Let me know if you need further assistance!
Thanks.
Manuel de la Peña (mandel) wrote : | # |
> * The file ubuntuone/
> this branch is changing the EOLs to windows style, and we should have unix
> EOLs in all the files.
>
Hm.. strange I used vim on linux to edit that.. let me double check and make sure its ok.
> * Also, like alecu mentioned, we are working on having the QT control panel
> running only a Qt main loop in Linux, since we need to drop the dependency on
> the qt4reactor in that OS because we can't file a MIR for it (at least for
> now). So, since the autoupdate code is only relevant to windows, we'd need to
> move all that (included the LoopingCall, which brings in the need of a twisted
> reactor) to a windows-specific location, for example, main/windows.py.
>
> I'd suggest to move the 'start' method to the 'main/' python package, and have
> all the autoupdate code moved to main/windows.py.
>
> Sorry if I was not explicit about this before, but the goal of dropping the
> qt4reactor in Linux is a rather new objective.
>
> Let me know if you need further assistance!
> Thanks.
Sure, no problem, its a rather old branch so moving qt to linux is a new thing. I'll get it fix for today.
- 268. By Manuel de la Peña
-
Removed the need to import the reactor from the systray.
- 269. By Manuel de la Peña
-
Fixed the start tests that failed due to changes in the API.
- 270. By Manuel de la Peña
-
Added a looping call for the linux implementation based on QTimer.
- 271. By Manuel de la Peña
-
Face palm.
- 272. By Manuel de la Peña
-
Added tests for the linux LoopingCall.
- 273. By Manuel de la Peña
-
Face palm.
- 274. By Manuel de la Peña
-
_execute required to have self passed.
- 275. By Manuel de la Peña
-
Fixed tests.
- 276. By Manuel de la Peña
-
Got all tests working correctly.
Manuel de la Peña (mandel) wrote : | # |
I have applied changes to the code to simplify the move away from the qt4reactor, please let me know if they are goo enough:
* Removed the import of stop in the systray (present in trunk). Because we pass the stop function to main lets just foward that to systray.
* Added a LoopingCall object that uses QTimer for linux since we won't be using the reactor. Added tests to ensure if works as a LoopingCall (including same assert exceptions.)
* dos2unix used in the files with windows EOL.
- 277. By Manuel de la Peña
-
Moved start to a diff location so that we do not drag the qt reactor with us.
- 278. By Manuel de la Peña
-
Merged with parent.
- 279. By Manuel de la Peña
-
Added tests for start.
- 280. By Manuel de la Peña
-
Fixed tests on windows.
- 281. By Manuel de la Peña
-
Always perform the auto update looping call.
- 282. By Manuel de la Peña
-
Fixed lint issues.
- 283. By Manuel de la Peña
-
Updated copyrighs.
- 284. By Manuel de la Peña
-
Removed the pep8 issue.
- 285. By Manuel de la Peña
-
Merged with trunk and fixed all the conflicts.
- 286. By Manuel de la Peña
-
Moved looping call to a task package.
- 287. By Manuel de la Peña
-
Merged and resolve conflicts with the unsintall branch.
- 288. By Manuel de la Peña
-
Added the tests to the correct location. Updated setup.py
- 289. By Manuel de la Peña
-
Set the value of the looping call.
Unmerged revisions
- 289. By Manuel de la Peña
-
Set the value of the looping call.
- 288. By Manuel de la Peña
-
Added the tests to the correct location. Updated setup.py
- 287. By Manuel de la Peña
-
Merged and resolve conflicts with the unsintall branch.
- 286. By Manuel de la Peña
-
Moved looping call to a task package.
- 285. By Manuel de la Peña
-
Merged with trunk and fixed all the conflicts.
- 284. By Manuel de la Peña
-
Removed the pep8 issue.
- 283. By Manuel de la Peña
-
Updated copyrighs.
- 282. By Manuel de la Peña
-
Fixed lint issues.
- 281. By Manuel de la Peña
-
Always perform the auto update looping call.
- 280. By Manuel de la Peña
-
Fixed tests on windows.
Preview Diff
1 | === modified file 'setup.py' |
2 | --- setup.py 2012-03-07 14:24:40 +0000 |
3 | +++ setup.py 2012-03-30 18:41:34 +0000 |
4 | @@ -207,6 +207,7 @@ |
5 | 'ubuntuone.controlpanel.gui', |
6 | 'ubuntuone.controlpanel.gui.qt', |
7 | 'ubuntuone.controlpanel.gui.qt.main', |
8 | + 'ubuntuone.controlpanel.gui.qt.task', |
9 | 'ubuntuone.controlpanel.gui.qt.ui', |
10 | 'ubuntuone.controlpanel.gui.qt.uniqueapp', |
11 | 'ubuntuone.controlpanel.sd_client', |
12 | |
13 | === modified file 'ubuntuone/controlpanel/gui/__init__.py' |
14 | --- ubuntuone/controlpanel/gui/__init__.py 2012-03-20 15:11:56 +0000 |
15 | +++ ubuntuone/controlpanel/gui/__init__.py 2012-03-30 18:41:34 +0000 |
16 | @@ -246,6 +246,8 @@ |
17 | TALK_TO_US = _('Talk to us') |
18 | VALUE_ERROR = _('Value could not be retrieved.') |
19 | UNKNOWN_ERROR = _('Unknown error') |
20 | +UPDATES_TITLE = _('Updates') |
21 | +UPDATES_MESSAGE = _('There is a new update available') |
22 | USAGE_LABEL = _('%(used)s of %(total)s') |
23 | QUIT_LABEL = _('Quit Ubuntu One') |
24 | |
25 | |
26 | === modified file 'ubuntuone/controlpanel/gui/qt/gui.py' |
27 | --- ubuntuone/controlpanel/gui/qt/gui.py 2012-03-29 19:05:02 +0000 |
28 | +++ ubuntuone/controlpanel/gui/qt/gui.py 2012-03-30 18:41:34 +0000 |
29 | @@ -18,8 +18,15 @@ |
30 | |
31 | from PyQt4 import QtGui, QtCore |
32 | |
33 | +from ubuntuone.controlpanel.utils import ( |
34 | + autoupdate, |
35 | + AUTOUPDATE_INTERVAL, |
36 | +) |
37 | +from ubuntuone.controlpanel.gui import UPDATES_TITLE, UPDATES_MESSAGE |
38 | from ubuntuone.controlpanel.gui.qt.systray import TrayIcon |
39 | +from ubuntuone.controlpanel.gui.qt.task import LoopingCall |
40 | from ubuntuone.controlpanel.gui.qt.ui import mainwindow_ui |
41 | +from ubuntuone.controlpanel.logger import setup_logging |
42 | from ubuntuone.controlpanel.utils import add_to_autostart |
43 | |
44 | # pylint: disable=E0611 |
45 | @@ -32,6 +39,8 @@ |
46 | |
47 | U1_DOTDESKTOP = "ubuntuone-installer.desktop" |
48 | |
49 | +logger = setup_logging('qt.gui') |
50 | + |
51 | |
52 | class MainWindow(QtGui.QMainWindow): |
53 | """The Main Window of the Control Panel.""" |
54 | @@ -108,6 +117,10 @@ |
55 | if with_icon or minimized: |
56 | QtGui.QApplication.instance().setQuitOnLastWindowClosed(False) |
57 | icon = TrayIcon(window=window, close_callback=close_callback) |
58 | + if AUTOUPDATE_INTERVAL: |
59 | + icon.auto_update_lc = LoopingCall(autoupdate, UPDATES_TITLE, |
60 | + UPDATES_MESSAGE, icon, logger) |
61 | + icon.auto_update_lc.start(AUTOUPDATE_INTERVAL, now=True) |
62 | else: |
63 | icon = None |
64 | return icon, window |
65 | |
66 | === modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py' |
67 | --- ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-03-29 17:20:14 +0000 |
68 | +++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-03-30 18:41:34 +0000 |
69 | @@ -1,6 +1,6 @@ |
70 | # -*- coding: utf-8 -*- |
71 | # |
72 | -# Copyright 2011 Canonical Ltd. |
73 | +# Copyright 2011-12 Canonical Ltd. |
74 | # |
75 | # This program is free software: you can redistribute it and/or modify it |
76 | # under the terms of the GNU General Public License version 3, as published |
77 | |
78 | === modified file 'ubuntuone/controlpanel/gui/qt/systray.py' |
79 | --- ubuntuone/controlpanel/gui/qt/systray.py 2012-03-02 13:53:24 +0000 |
80 | +++ ubuntuone/controlpanel/gui/qt/systray.py 2012-03-30 18:41:34 +0000 |
81 | @@ -34,6 +34,9 @@ |
82 | self.setIcon(QtGui.QIcon(":/icon.png")) |
83 | self.setVisible(True) |
84 | self.window = window |
85 | + self.auto_update_lc = None |
86 | + self._message_cb = lambda: None |
87 | + self.messageClicked.connect(self.on_message_clicked) |
88 | self.activated.connect(self.on_activated) |
89 | self.context_menu = QtGui.QMenu() |
90 | self.restore = QtGui.QAction(RESTORE_LABEL, self, |
91 | @@ -52,6 +55,10 @@ |
92 | if reason == self.Trigger: # Left-click |
93 | self.restore_window() |
94 | |
95 | + def on_message_clicked(self): |
96 | + """Execute the callback that was set.""" |
97 | + self._message_cb() |
98 | + |
99 | def restore_window(self): |
100 | """Show the main window.""" |
101 | if self.window is None: |
102 | @@ -79,4 +86,14 @@ |
103 | except: |
104 | # Maybe it was not running? |
105 | pass |
106 | + if self.auto_update_lc is not None and\ |
107 | + self.auto_update_lc.running: |
108 | + self.auto_update_lc.stop() |
109 | + |
110 | self.close_callback() |
111 | + |
112 | + |
113 | + def show_message(self, title, message, clicked_cb=lambda: None): |
114 | + """Show a message and set the new callback to execute.""" |
115 | + self._message_cb = clicked_cb |
116 | + self.showMessage(title, message) |
117 | |
118 | === added directory 'ubuntuone/controlpanel/gui/qt/task' |
119 | === added file 'ubuntuone/controlpanel/gui/qt/task/__init__.py' |
120 | --- ubuntuone/controlpanel/gui/qt/task/__init__.py 1970-01-01 00:00:00 +0000 |
121 | +++ ubuntuone/controlpanel/gui/qt/task/__init__.py 2012-03-30 18:41:34 +0000 |
122 | @@ -0,0 +1,30 @@ |
123 | +# -*- coding: utf-8 -*- |
124 | +# |
125 | +# Copyright 2012 Canonical Ltd. |
126 | +# |
127 | +# This program is free software: you can redistribute it and/or modify it |
128 | +# under the terms of the GNU General Public License version 3, as published |
129 | +# by the Free Software Foundation. |
130 | +# |
131 | +# This program is distributed in the hope that it will be useful, but |
132 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
133 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
134 | +# PURPOSE. See the GNU General Public License for more details. |
135 | +# |
136 | +# You should have received a copy of the GNU General Public License along |
137 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
138 | + |
139 | +"""Allow to perform tasks in the main loop.""" |
140 | + |
141 | +import sys |
142 | + |
143 | + |
144 | +#pylint: disable=C0103 |
145 | +if sys.platform == 'win32': |
146 | + from twisted.internet import task |
147 | + source = task |
148 | +else: |
149 | + from ubuntuone.controlpanel.gui.qt.task import linux |
150 | + source = linux |
151 | + |
152 | +LoopingCall = source.LoopingCall |
153 | |
154 | === added file 'ubuntuone/controlpanel/gui/qt/task/linux.py' |
155 | --- ubuntuone/controlpanel/gui/qt/task/linux.py 1970-01-01 00:00:00 +0000 |
156 | +++ ubuntuone/controlpanel/gui/qt/task/linux.py 2012-03-30 18:41:34 +0000 |
157 | @@ -0,0 +1,54 @@ |
158 | +# -*- coding: utf-8 -*- |
159 | +# |
160 | +# Copyright 2011-12 Canonical Ltd. |
161 | +# |
162 | +# This program is free software: you can redistribute it and/or modify it |
163 | +# under the terms of the GNU General Public License version 3, as published |
164 | +# by the Free Software Foundation. |
165 | +# |
166 | +# This program is distributed in the hope that it will be useful, but |
167 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
168 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
169 | +# PURPOSE. See the GNU General Public License for more details. |
170 | +# |
171 | +# You should have received a copy of the GNU General Public License along |
172 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
173 | + |
174 | +"""Main method to be used on linux.""" |
175 | + |
176 | +from PyQt4 import QtCore |
177 | + |
178 | + |
179 | +class LoopingCall(QtCore.QObject): |
180 | + """A looping call executed every x seconds.""" |
181 | + |
182 | + def __init__(self, callback, *args, **kwargs): |
183 | + """Create a new function to call.""" |
184 | + QtCore.QObject.__init__(self) |
185 | + self.cb = callback |
186 | + self.args = args |
187 | + self.kwargs = kwargs |
188 | + self.timer = QtCore.QTimer(self) |
189 | + self.timer.timeout.connect(self._execute) |
190 | + self.running = False |
191 | + |
192 | + def _execute(self): |
193 | + """Execute the function passed to the looping call.""" |
194 | + self.cb(*self.args, **self.kwargs) |
195 | + |
196 | + def start(self, interval, now=True): |
197 | + """Start the looping call.""" |
198 | + assert not self.running, ("Tried to start an already running " |
199 | + "LoopingCall.") |
200 | + |
201 | + self.running = True |
202 | + if now: |
203 | + self._execute() |
204 | + self.timer.start(interval) |
205 | + |
206 | + def stop(self): |
207 | + """Stop the looping call.""" |
208 | + assert self.running, ("Tried to stop a LoopingCall that was " |
209 | + "not running.") |
210 | + self.timer.stop() |
211 | + self.running = False |
212 | |
213 | === added directory 'ubuntuone/controlpanel/gui/qt/task/tests' |
214 | === added file 'ubuntuone/controlpanel/gui/qt/task/tests/__init__.py' |
215 | --- ubuntuone/controlpanel/gui/qt/task/tests/__init__.py 1970-01-01 00:00:00 +0000 |
216 | +++ ubuntuone/controlpanel/gui/qt/task/tests/__init__.py 2012-03-30 18:41:34 +0000 |
217 | @@ -0,0 +1,17 @@ |
218 | +# -*- coding: utf-8 -*- |
219 | +# |
220 | +# Copyright 2011-2012 Canonical Ltd. |
221 | +# |
222 | +# This program is free software: you can redistribute it and/or modify it |
223 | +# under the terms of the GNU General Public License version 3, as published |
224 | +# by the Free Software Foundation. |
225 | +# |
226 | +# This program is distributed in the hope that it will be useful, but |
227 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
228 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
229 | +# PURPOSE. See the GNU General Public License for more details. |
230 | +# |
231 | +# You should have received a copy of the GNU General Public License along |
232 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
233 | + |
234 | +"""The test suite for the Qt task module.""" |
235 | |
236 | === added file 'ubuntuone/controlpanel/gui/qt/task/tests/test_linux.py' |
237 | --- ubuntuone/controlpanel/gui/qt/task/tests/test_linux.py 1970-01-01 00:00:00 +0000 |
238 | +++ ubuntuone/controlpanel/gui/qt/task/tests/test_linux.py 2012-03-30 18:41:34 +0000 |
239 | @@ -0,0 +1,82 @@ |
240 | +# -*- coding: utf-8 -*- |
241 | +# |
242 | +# Copyright 2011 Canonical Ltd. |
243 | +# |
244 | +# This program is free software: you can redistribute it and/or modify it |
245 | +# under the terms of the GNU General Public License version 3, as published |
246 | +# by the Free Software Foundation. |
247 | +# |
248 | +# This program is distributed in the hope that it will be useful, but |
249 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
250 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
251 | +# PURPOSE. See the GNU General Public License for more details. |
252 | +# |
253 | +# You should have received a copy of the GNU General Public License along |
254 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
255 | + |
256 | +"""Tests for the main module on linux.""" |
257 | + |
258 | +from twisted.internet import defer |
259 | + |
260 | +from ubuntuone.controlpanel.gui.qt.task import linux |
261 | +from ubuntuone.controlpanel.tests import TestCase |
262 | + |
263 | + |
264 | +class FakeCallback(object): |
265 | + """Fake callback.""" |
266 | + |
267 | + def __init__(self): |
268 | + """Create a new instance.""" |
269 | + self.args = None |
270 | + self.kwargs = None |
271 | + self.deferred = defer.Deferred() |
272 | + |
273 | + def __call__(self, *args, **kwargs): |
274 | + """Call the cb.""" |
275 | + self.args = args |
276 | + self.kwargs = kwargs |
277 | + if not self.deferred.called: |
278 | + self.deferred.callback(True) |
279 | + |
280 | + |
281 | +class TestLoopingCall(TestCase): |
282 | + """Test the looping call implementation.""" |
283 | + |
284 | + @defer.inlineCallbacks |
285 | + def setUp(self): |
286 | + """Set up tests.""" |
287 | + yield super(TestLoopingCall, self).setUp() |
288 | + self.cb = FakeCallback() |
289 | + self.interval = 100 |
290 | + |
291 | + @defer.inlineCallbacks |
292 | + def test_start(self): |
293 | + """Test that the call is indeed done.""" |
294 | + lc = linux.LoopingCall(self.cb, 'test', other='test') |
295 | + lc.start(self.interval) |
296 | + self.addCleanup(lc.stop) |
297 | + yield self.cb.deferred |
298 | + self.assertTrue(lc.running) |
299 | + self.assertEqual(('test',), self.cb.args) |
300 | + self.assertEqual(dict(other='test'), self.cb.kwargs) |
301 | + |
302 | + def test_start_twice(self): |
303 | + """Test that we can't call start more than once.""" |
304 | + lc = linux.LoopingCall(self.cb, 'test', other='test') |
305 | + lc.start(self.interval) |
306 | + self.addCleanup(lc.stop) |
307 | + self.assertRaises(AssertionError, lc.start, self.interval) |
308 | + |
309 | + def test_stop(self): |
310 | + """Test that we stop.""" |
311 | + lc = linux.LoopingCall(self.cb, 'test', other='test') |
312 | + lc.start(self.interval) |
313 | + lc.stop() |
314 | + self.assertFalse(lc.running) |
315 | + |
316 | + def test_stop_twice(self): |
317 | + """Test that we can't call stop more than once.""" |
318 | + lc = linux.LoopingCall(self.cb, 'test', other='test') |
319 | + lc.start(self.interval) |
320 | + lc.stop() |
321 | + self.assertRaises(AssertionError, lc.stop) |
322 | |
323 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_gui.py' |
324 | --- ubuntuone/controlpanel/gui/qt/tests/test_gui.py 2012-03-26 13:30:07 +0000 |
325 | +++ ubuntuone/controlpanel/gui/qt/tests/test_gui.py 2012-03-30 18:41:34 +0000 |
326 | @@ -35,6 +35,70 @@ |
327 | self.called = (args, kwargs) |
328 | |
329 | |
330 | +class FakeMainWindow(object): |
331 | + """Fake window.""" |
332 | + |
333 | + def __init__(self, close_callback=None): |
334 | + """Create a new instance.""" |
335 | + self.called = [] |
336 | + self.close_callback = None |
337 | + |
338 | + def __call__(self, *args, **kwargs): |
339 | + """Make the object callable to fake a constructor.""" |
340 | + return self |
341 | + |
342 | + def show(self): |
343 | + """Fake show the window.""" |
344 | + self.called.append(('show',)) |
345 | + |
346 | + |
347 | +class FakeTrayIcon(object): |
348 | + """Fake tray icon.""" |
349 | + |
350 | + def __init__(self, window=None): |
351 | + """Create a new instance.""" |
352 | + self.auto_update = None |
353 | + self.window = None |
354 | + |
355 | + def __call__(self, window, *args, **kwargs): |
356 | + """Make the object callable to fake a cosntructor.""" |
357 | + self.window = window |
358 | + return self |
359 | + |
360 | + def get_auto_update_lc(self): |
361 | + """Get the auto_update_lc.""" |
362 | + return self.auto_update |
363 | + |
364 | + def set_auto_update_lc(self, value): |
365 | + """Set the auto_update_lc.""" |
366 | + self.auto_update = value |
367 | + |
368 | + auto_update_lc = property(get_auto_update_lc, |
369 | + set_auto_update_lc) |
370 | + |
371 | + |
372 | +class FakeLoopingCall(object): |
373 | + """Fake a looping call.""" |
374 | + |
375 | + def __init__(self): |
376 | + """Create a new instance.""" |
377 | + self.function = None |
378 | + self.args = None |
379 | + self.kwargs = None |
380 | + self.called = [] |
381 | + |
382 | + def __call__(self, f, *args, **kwargs): |
383 | + """Call looping call.""" |
384 | + self.function = f |
385 | + self.args = args |
386 | + self.kwargs = kwargs |
387 | + return self |
388 | + |
389 | + def start(self, interval, now=False): |
390 | + """Fake the start.""" |
391 | + self.called.append(('start', interval, now)) |
392 | + |
393 | + |
394 | class MainWindowTestCase(BaseTestCase): |
395 | """Test the qt main window.""" |
396 | |
397 | @@ -131,3 +195,71 @@ |
398 | def test_add_to_autostart(self): |
399 | """Test that the add_to_autostart function is called when CP opens.""" |
400 | self.assertTrue(self._called) |
401 | + |
402 | + |
403 | +class StartTestCase(BaseTestCase): |
404 | + """Test the start method.""" |
405 | + |
406 | + @defer.inlineCallbacks |
407 | + def setUp(self): |
408 | + """Set the tests.""" |
409 | + yield super(StartTestCase, self).setUp() |
410 | + self.called = [] |
411 | + self.auto_update_interval = 0.5 |
412 | + self.looping_call = FakeLoopingCall() |
413 | + |
414 | + def stop(): |
415 | + """Fake stop function.""" |
416 | + self.called.append(('stop',)) |
417 | + |
418 | + def auto_update(title, message, icon, logger): |
419 | + """Fake auto_update_cb.""" |
420 | + self.called.append(('auto_update_cb', title, message, |
421 | + icon, logger)) |
422 | + |
423 | + self.stop_cb = stop |
424 | + self.auto_update_cb = auto_update |
425 | + self.main_window = FakeMainWindow() |
426 | + self.tray_icon = FakeTrayIcon() |
427 | + old_time = gui.AUTOUPDATE_INTERVAL |
428 | + self.addCleanup(self._set_time, old_time) |
429 | + gui.AUTOUPDATE_INTERVAL = 1 |
430 | + |
431 | + def _set_time(self, time): |
432 | + """Set the correct value back.""" |
433 | + gui.AUTOUPDATE_INTERVAL = time |
434 | + |
435 | + def test_start_not_minimized_no_icon(self): |
436 | + """Test the not minimized mode with no icon.""" |
437 | + icon, window = gui.start(self.stop_cb, minimized=False, |
438 | + with_icon=False) |
439 | + self.assertEqual(1, len(self.main_window.called)) |
440 | + self.assertTrue('show' in self.main_window.called[0]) |
441 | + self.assertEqual(None, self.looping_call.function) |
442 | + self.assertNotEqual(None, window) |
443 | + self.assertEqual(None, icon) |
444 | + |
445 | + def _assert_minimized_or_icon(self, icon, window): |
446 | + """Assert the start of a minimized of icon mode.""" |
447 | + self.assertIsInstance(self.tray_icon.auto_update, FakeLoopingCall) |
448 | + self.assertNotEqual(None, self.looping_call.function) |
449 | + self.assertNotEqual(None, icon) |
450 | + |
451 | + def test_start_with_icon(self): |
452 | + """Test the with icon start.""" |
453 | + icon, window = gui.start(self.stop_cb, minimized=False, |
454 | + with_icon=True) |
455 | + self._assert_minimized_or_icon(icon, window) |
456 | + self.assertEqual(1, len(self.main_window.called)) |
457 | + self.assertTrue('show' in self.main_window.called[0]) |
458 | + self.assertNotEqual(None, window) |
459 | + self.assertNotEqual(None, self.tray_icon.window) |
460 | + |
461 | + def test_start_minimized(self): |
462 | + """Test the minimized start.""" |
463 | + icon, window = gui.start(self.stop_cb, minimized=True, |
464 | + with_icon=False) |
465 | + self._assert_minimized_or_icon(icon, window) |
466 | + self.assertEqual(0, len(self.main_window.called)) |
467 | + self.assertEqual(None, window) |
468 | + self.assertEqual(None, self.tray_icon.window) |
469 | |
470 | === removed file 'ubuntuone/controlpanel/gui/qt/tests/test_start.py' |
471 | --- ubuntuone/controlpanel/gui/qt/tests/test_start.py 2012-03-20 19:13:28 +0000 |
472 | +++ ubuntuone/controlpanel/gui/qt/tests/test_start.py 1970-01-01 00:00:00 +0000 |
473 | @@ -1,108 +0,0 @@ |
474 | -# -*- coding: utf-8 -*- |
475 | - |
476 | -# Author: Roberto Alsina <roberto.alsina@canonical.com> |
477 | -# |
478 | -# Copyright 2011 Canonical Ltd. |
479 | -# |
480 | -# This program is free software: you can redistribute it and/or modify it |
481 | -# under the terms of the GNU General Public License version 3, as published |
482 | -# by the Free Software Foundation. |
483 | -# |
484 | -# This program is distributed in the hope that it will be useful, but |
485 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
486 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
487 | -# PURPOSE. See the GNU General Public License for more details. |
488 | -# |
489 | -# You should have received a copy of the GNU General Public License along |
490 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
491 | - |
492 | -"""Tests for the start function.""" |
493 | - |
494 | -from twisted.internet import defer |
495 | - |
496 | -from ubuntuone.controlpanel.gui.qt import gui |
497 | -from ubuntuone.controlpanel.tests import TestCase |
498 | - |
499 | - |
500 | -class FakeThing(object): |
501 | - |
502 | - """A fake thing.""" |
503 | - |
504 | - shown = False |
505 | - size = lambda *a: gui.QtCore.QSize(123456, 654321) |
506 | - style = None |
507 | - |
508 | - def __init__(self): |
509 | - self.args = [] |
510 | - |
511 | - def __call__(self, *args, **kwargs): |
512 | - self.args.append((args, kwargs)) |
513 | - return self |
514 | - |
515 | - def show(self): |
516 | - """Show.""" |
517 | - self.shown = True |
518 | - |
519 | - # Invalid name "setGeometry" |
520 | - # pylint: disable=C0103 |
521 | - |
522 | - def setGeometry(self, style): |
523 | - """Save the new geometry.""" |
524 | - self.style = style |
525 | - |
526 | - |
527 | -class StartTestCase(TestCase): |
528 | - """Test the qt control panel.""" |
529 | - |
530 | - @defer.inlineCallbacks |
531 | - def setUp(self): |
532 | - yield super(StartTestCase, self).setUp() |
533 | - self.main_window = FakeThing() |
534 | - self.tray_icon = FakeThing() |
535 | - self.patch(gui, "MainWindow", self.main_window) |
536 | - self.patch(gui, "TrayIcon", self.tray_icon) |
537 | - |
538 | - def close_cb(self): |
539 | - """A dummy close callback.""" |
540 | - |
541 | - def test_minimized(self): |
542 | - """Test behaviour with minimized=True.""" |
543 | - gui.start(close_callback=self.close_cb, |
544 | - minimized=True, with_icon=True) |
545 | - kwargs = {'close_callback': self.close_cb, 'window': None} |
546 | - self.assertEqual(self.tray_icon.args, [((), kwargs)]) |
547 | - self.assertEqual(self.main_window.args, []) |
548 | - |
549 | - def test_with_icon(self): |
550 | - """Test behaviour with with_icon=True.""" |
551 | - gui.start(close_callback=self.close_cb, |
552 | - with_icon=True, minimized=False) |
553 | - kwargs = {'close_callback': self.close_cb, 'window': self.main_window} |
554 | - self.assertEqual(self.main_window.args, [((), {'installer': False})]) |
555 | - self.assertEqual(self.tray_icon.args, [((), kwargs)]) |
556 | - |
557 | - def test_both_false(self): |
558 | - """Test behaviour when with_icon and minimized are False.""" |
559 | - gui.start(close_callback=self.close_cb, |
560 | - with_icon=False, minimized=False) |
561 | - # Should be called |
562 | - self.assertNotEqual(self.main_window.args, []) |
563 | - # Should not be called |
564 | - self.assertEqual(self.tray_icon.args, []) |
565 | - |
566 | - def test_both_true(self): |
567 | - """Test behaviour when with_icon and minimized are True.""" |
568 | - gui.start(close_callback=self.close_cb, |
569 | - with_icon=True, minimized=True) |
570 | - kwargs = {'close_callback': self.close_cb, 'window': None} |
571 | - self.assertEqual(self.tray_icon.args, [((), kwargs)]) |
572 | - self.assertEqual(self.main_window.args, []) |
573 | - |
574 | - def test_center_window(self): |
575 | - """The main window should be centered.""" |
576 | - gui.start(close_callback=self.close_cb) |
577 | - app = gui.QtGui.QApplication.instance() |
578 | - expected = gui.QtGui.QStyle.alignedRect(gui.QtCore.Qt.LeftToRight, |
579 | - gui.QtCore.Qt.AlignCenter, self.main_window.size(), |
580 | - app.desktop().availableGeometry()) |
581 | - self.assertEqual(self.main_window.style, expected) |
582 | |
583 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py' |
584 | --- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-02-06 21:10:10 +0000 |
585 | +++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-03-30 18:41:34 +0000 |
586 | @@ -19,7 +19,7 @@ |
587 | """Tests for the notification area icon.""" |
588 | |
589 | from PyQt4 import QtGui |
590 | -from twisted.internet.defer import inlineCallbacks |
591 | +from twisted.internet import defer |
592 | |
593 | from ubuntuone.controlpanel.gui.qt import systray |
594 | from ubuntuone.controlpanel.tests import TestCase |
595 | @@ -46,12 +46,37 @@ |
596 | super(FakeMainWindow, self).__init__() |
597 | |
598 | |
599 | +class FakeLoopingCall(object): |
600 | + """Fake a twisted looping call.""" |
601 | + |
602 | + def __init__(self, is_running=False): |
603 | + """Create a new instance.""" |
604 | + self.called = [] |
605 | + self.running = is_running |
606 | + |
607 | + def start(self, interval, now=False): |
608 | + """Fake starting the call.""" |
609 | + self.called.append(('start', interval, now)) |
610 | + self.running = True |
611 | + |
612 | + def stop(self): |
613 | + """Fake stopping the call.""" |
614 | + self.called.append(('stop',)) |
615 | + self.running = False |
616 | + |
617 | + |
618 | class SystrayTestCase(TestCase): |
619 | |
620 | """Test the notification area icon.""" |
621 | |
622 | - def test_quit(self): |
623 | - """Test the quit option in the menu.""" |
624 | + @defer.inlineCallbacks |
625 | + def setUp(self): |
626 | + """Set the different tests.""" |
627 | + yield super(SystrayTestCase, self).setUp() |
628 | + self.looping_call = FakeLoopingCall() |
629 | + |
630 | + def test_quit_no_lc(self): |
631 | + """Test the quit option with no looping call.""" |
632 | # Not done on setup, because if I patch stop |
633 | # after instantiation, it doesn't get called. |
634 | self.patch(systray.TrayIcon, "stop", self._set_called) |
635 | @@ -59,12 +84,36 @@ |
636 | tray.quit.trigger() |
637 | self.assertEqual(self._called, ((False,), {})) |
638 | |
639 | - @inlineCallbacks |
640 | + @defer.inlineCallbacks |
641 | + def test_quit_lc_not_running(self): |
642 | + """Test the quit option with a stopped looping call.""" |
643 | + st = FakeSDTool() |
644 | + self.patch(systray, "SyncDaemonTool", lambda: st) |
645 | + tray = systray.TrayIcon(close_callback=self._set_called) |
646 | + tray.auto_update_lc = self.looping_call |
647 | + yield tray.stop() |
648 | + self.assertEqual(self._called, ((), {})) |
649 | + self.assertEqual(0, len(self.looping_call.called)) |
650 | + |
651 | + @defer.inlineCallbacks |
652 | + def test_quit_lc_running(self): |
653 | + """Test the quit option with a running looping call.""" |
654 | + st = FakeSDTool() |
655 | + self.patch(systray, "SyncDaemonTool", lambda: st) |
656 | + self.looping_call.running = True |
657 | + tray = systray.TrayIcon(close_callback=self._set_called) |
658 | + tray.auto_update_lc = self.looping_call |
659 | + yield tray.stop() |
660 | + self.assertEqual(self._called, ((), {})) |
661 | + self.assertEqual(1, len(self.looping_call.called)) |
662 | + self.assertTrue('stop' in self.looping_call.called[0]) |
663 | + |
664 | + @defer.inlineCallbacks |
665 | def test_stop_sd(self): |
666 | """Quit should call SyncDaemonTool.quit().""" |
667 | st = FakeSDTool() |
668 | self.patch(systray, "SyncDaemonTool", lambda: st) |
669 | - tray = systray.TrayIcon() |
670 | + tray = systray.TrayIcon(close_callback=lambda: None) |
671 | yield tray.stop() |
672 | self.assertTrue(st.called) |
673 | |
674 | @@ -122,3 +171,26 @@ |
675 | self.assertEqual(tray.window, None) |
676 | self.assertIsInstance(tray.context_menu, QtGui.QMenu) |
677 | self.assertFalse(tray.icon() == None) |
678 | + |
679 | + def test_show_message(self): |
680 | + """Test the method that shows a message.""" |
681 | + called = [] |
682 | + title = 'updates' |
683 | + message = 'you want to update!' |
684 | + |
685 | + def fake_qt_show_message(instance, title, message): |
686 | + """A fake qt show message.""" |
687 | + called.extend([instance, title, message]) |
688 | + |
689 | + def fake_clicked_message_cb(): |
690 | + """A fake clicked cb.""" |
691 | + |
692 | + self.patch(systray.TrayIcon, 'showMessage', fake_qt_show_message) |
693 | + tray = systray.TrayIcon() |
694 | + tray.show_message(title, message, fake_clicked_message_cb) |
695 | + self.assertTrue(tray in called) |
696 | + self.assertTrue(title in called) |
697 | + self.assertTrue(message in called) |
698 | + # pylint: disable=W0212 |
699 | + self.assertEqual(fake_clicked_message_cb, tray._message_cb) |
700 | + # pylint: enable=W0212 |
701 | |
702 | === modified file 'ubuntuone/controlpanel/utils/__init__.py' |
703 | --- ubuntuone/controlpanel/utils/__init__.py 2012-03-29 17:58:28 +0000 |
704 | +++ ubuntuone/controlpanel/utils/__init__.py 2012-03-30 18:41:34 +0000 |
705 | @@ -39,16 +39,20 @@ |
706 | from ubuntuone.controlpanel.utils import windows |
707 | add_to_autostart = windows.add_to_autostart |
708 | are_updates_present = windows.are_updates_present |
709 | + autoupdate = windows.autoupdate |
710 | default_folders = windows.default_folders |
711 | perform_update = windows.perform_update |
712 | uninstall_application = windows.uninstall_application |
713 | + AUTOUPDATE_INTERVAL = windows.AUTOUPDATE_INTERVAL |
714 | else: |
715 | from ubuntuone.controlpanel.utils import linux |
716 | add_to_autostart = no_op |
717 | are_updates_present = no_op |
718 | + autoupdate = no_op |
719 | default_folders = linux.default_folders |
720 | perform_update = no_op |
721 | uninstall_application = no_op |
722 | + AUTOUPDATE_INTERVAL = None |
723 | |
724 | # pylint: enable=C0103 |
725 | |
726 | |
727 | === modified file 'ubuntuone/controlpanel/utils/tests/test_windows.py' |
728 | --- ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-30 17:34:33 +0000 |
729 | +++ ubuntuone/controlpanel/utils/tests/test_windows.py 2012-03-30 18:41:34 +0000 |
730 | @@ -21,7 +21,7 @@ |
731 | |
732 | from twisted.internet import defer |
733 | |
734 | -from ubuntuone.controlpanel import utils |
735 | +from ubuntuone.controlpanel import gui, utils |
736 | from ubuntuone.controlpanel.tests import TestCase |
737 | |
738 | # let me use protected methods |
739 | @@ -56,6 +56,19 @@ |
740 | self.addCleanup(setattr, utils.windows.sys, attr_name, value) |
741 | |
742 | |
743 | +class FakeSysTray(object): |
744 | + """A Fake systray object.""" |
745 | + |
746 | + def __init__(self): |
747 | + """Create a new instance.""" |
748 | + self.called = [] |
749 | + |
750 | + def show_message(self, title, message, callback): |
751 | + """Fake show message.""" |
752 | + self.called.append(('show_message', title, message, |
753 | + callback)) |
754 | + |
755 | + |
756 | class AutoupdaterTestCase(TestCase): |
757 | """Test the code that is used for the auto update process.""" |
758 | |
759 | @@ -69,6 +82,7 @@ |
760 | self.return_from_call = 0 |
761 | self.command = None |
762 | self.args = [] |
763 | + self.systray = FakeSysTray() |
764 | |
765 | def fake_execute_process(command, args=None, path=None): |
766 | """Fake async process execution.""" |
767 | @@ -114,6 +128,37 @@ |
768 | '--unattendedmodeui none', '', 0) |
769 | self.assertEqual(self._called, (args, {})) |
770 | |
771 | + @defer.inlineCallbacks |
772 | + def test_autoupdate_no_updates(self): |
773 | + """Test the autoupdate method with no updates.""" |
774 | + |
775 | + def fake_are_updates_present(): |
776 | + """Fake are_updates_present.""" |
777 | + return defer.succeed(False) |
778 | + |
779 | + self.patch(utils.windows, 'are_updates_present', |
780 | + fake_are_updates_present) |
781 | + yield utils.autoupdate(gui.UPDATES_TITLE, gui.UPDATES_MESSAGE, |
782 | + self.systray) |
783 | + self.assertEqual(0, len(self.systray.called)) |
784 | + |
785 | + @defer.inlineCallbacks |
786 | + def test_autoupdate_systray(self): |
787 | + """Test the autoupdate method with the systray integration.""" |
788 | + |
789 | + def fake_are_updates_present(): |
790 | + """Fake are_updates_present.""" |
791 | + return defer.succeed(True) |
792 | + |
793 | + self.patch(utils.windows, 'are_updates_present', |
794 | + fake_are_updates_present) |
795 | + yield utils.autoupdate(gui.UPDATES_TITLE, gui.UPDATES_MESSAGE, |
796 | + self.systray) |
797 | + self.assertEqual(1, len(self.systray.called)) |
798 | + self.assertTrue(gui.UPDATES_TITLE in self.systray.called[0]) |
799 | + self.assertTrue(gui.UPDATES_MESSAGE in self.systray.called[0]) |
800 | + self.assertTrue(utils.windows.perform_update in self.systray.called[0]) |
801 | + |
802 | |
803 | class FakeOpenKey(object): |
804 | |
805 | |
806 | === modified file 'ubuntuone/controlpanel/utils/windows.py' |
807 | --- ubuntuone/controlpanel/utils/windows.py 2012-03-30 17:34:33 +0000 |
808 | +++ ubuntuone/controlpanel/utils/windows.py 2012-03-30 18:41:34 +0000 |
809 | @@ -33,6 +33,7 @@ |
810 | |
811 | logger = setup_logging('utils.windows') |
812 | AUTOUPDATE_EXE_NAME = 'autoupdate-windows.exe' |
813 | +AUTOUPDATE_INTERVAL = 60 * 24 # 60s * 24 hours |
814 | AUTORUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run" |
815 | UNINSTALL_EXE_NAME = 'uninstall.exe' |
816 | |
817 | @@ -87,6 +88,17 @@ |
818 | defer.returnValue(result) |
819 | |
820 | |
821 | +@defer.inlineCallbacks |
822 | +def autoupdate(title, message, tray_icon): |
823 | + """Perform the full process of autoupdating the app.""" |
824 | + # Check fo updates and pop a message to the user when |
825 | + # and update is present |
826 | + are_present = yield are_updates_present() |
827 | + if are_present: |
828 | + tray_icon.show_message(title, message, |
829 | + perform_update) |
830 | + |
831 | + |
832 | def default_folders(user_home=None): |
833 | """Return a list of the folders to add by default.""" |
834 | # as per http://msdn.microsoft.com/en-us/library/windows/desktop/bb762181, |
This style of coding tests is very ugly and error prone:
534 + def _clean_ fake_window_ class(self) : called = [] close_callback = None fake_icon_ class(self) : auto_update = None fake_looping_ call_class( self): .function = None .args = None .kwargs = None .called = []
535 + """Clean the fake window class."""
536 + FakeMainWindow.
537 + FakeMainWindow.
538 +
539 + def _clean_
540 + """Clean the fake icon class."""
541 + FakeTrayIcon.window = None
542 + FakeTrayIcon.
543 +
544 + def _clean_
545 + """Clean the fake looping call class."""
546 + FakeLoopingCall
547 + FakeLoopingCall
548 + FakeLoopingCall
549 + FakeLoopingCall
Instead of patching with a class that gets its attributes overwritten, please patch with an instance that gets thrown away, so there's no need to reset the class attributes like in the code above.
For instance: Class() )"
"self.patch(gui, 'LoopingCall', FakeLoopingCall)" ->
"self.patch(gui, 'LoopingCall', FakeLoopingCall
This is present a few times in this branch, so make sure all occurrences end up fixed.
---
Also, to test code that uses LoopingCalls (and reactor.callLaters, too) remember that you can use twisted. internet. task.Clock, by patching the LoopingCall.clock attribute. Then you will be able to fake the reactor moving forward in time by calling clock.advance.
This gives you a lot of flexibility to test this kind of code, and the tests end up a lot faster and reliable than if they were using a small value for the LoopingCall or callLater time.