Merge lp:~ralsina/ubuntuone-windows-installer/local-folder-fixes into lp:ubuntuone-windows-installer
- local-folder-fixes
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Roberto Alsina | ||||
Approved revision: | 65 | ||||
Merged at revision: | 35 | ||||
Proposed branch: | lp:~ralsina/ubuntuone-windows-installer/local-folder-fixes | ||||
Merge into: | lp:ubuntuone-windows-installer | ||||
Diff against target: |
1091 lines (+749/-190) 4 files modified
data/qt/local_folders.ui (+1/-8) ubuntuone_installer/gui/qt/local_folders.py (+148/-64) ubuntuone_installer/gui/qt/tests/test_gui.py (+15/-118) ubuntuone_installer/gui/qt/tests/test_local_folders.py (+585/-0) |
||||
To merge this branch: | bzr merge lp:~ralsina/ubuntuone-windows-installer/local-folder-fixes | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Natalia Bidart (community) | Approve | ||
Review via email: mp+71857@code.launchpad.net |
Commit message
Implement the "Computer-to-cloud" page of the installer according to spec and discussions.
Description of the change
Implement the "Computer-to-cloud" page of the installer according to spec and discussions.
Roberto Alsina (ralsina) wrote : | # |
> * We should add a new test case for FolderItem in particular, asserting at
> least the following:
> - the checkstate is unchecked
> - the volume id is the one passed as parameter
> - the thread is started if calculate is True, not started if not
> - size is None if calculate, 0 if not
Added.
> * Also, I would advice moving the self.thread assignment outside the if-else
> since is the same one no matter the value of calculate
Moved.
> * 4 double quotes instead of 3 in:
>
> 404 + """"Fake volumes info."""
Fixed.
> * Always call super() and not the parent class directly. So, in this case:
>
> 475 def tearDown(self):
> 476 """Remove the temporary tree."""
> 477 shutil.
> 478 BaseTestCase.
>
> we should call super(). But, considering we can move the rmtree to the setUp
> method as a cleanup function (which is preferred than using tearDown), we can
> remove this tearDown altogether.
Done.
> * in test_status_
>
> self.assertFals
> self.assertFals
>
> will likely fail on slow machines, since get_info() returns a deferred which
> initializePage() is not waiting for. So, I would advice converting
> initializePage into an asynchronous method and yielding on this in the test.
Sadly that's not possible, because initializePage can't be a generator (it's called by Qt).
> Or, alternatively, since you are already providing a test for get_info, you
> can just assert get_info was called, and remove the checks for volume_info,
> account_info, items, timer, etc.
> Basically, test_status_
>
> - self.ui.offer_frame is not visible
> - self.wizard(
> - get_info was called
Done.
> * we need another test similar to test_status_
> all volumes which type is not UDF are ignored.
Added a volume of type "SHARE" to the fake volume_info, and it's checked to be ignored
(the number of items created stays the same)
> * I'm confused about the call to item.thread.join() in get_info. Can you
> please help me understand why is needed at that point?
It was there to ensure the thread was finished before setting item.size.
Fixed a buglet where I was not setting calculate=False when adding a UDF, so
it's not needed anymore.
> * this is not correct: self.assertTrue
> a boolean, we should assert equality against the value used when mocking
> volumes_info.
Changed.
> * test_size_
> think that thread is not being joined on. If so, can you please fix that?
No, it's not starting a thread, it's calling what the thread would run when started, which
is an ordinary function.
> * are there tests for change_page? I see one for the timer being stopped, but
> we need some for the try-except clause, and all the block right after the
> page_id == self.wizard(
There are these, that I think cover the block after the if:
test_changed_
Natalia Bidart (nataliabidart) wrote : | # |
> > * Always call super() and not the parent class directly. So, in this case:
> >
> > 475 def tearDown(self):
> > 476 """Remove the temporary tree."""
> > 477 shutil.
> > 478 BaseTestCase.
> >
> > we should call super(). But, considering we can move the rmtree to the setUp
> > method as a cleanup function (which is preferred than using tearDown), we
> can
> > remove this tearDown altogether.
>
> Done.
Great! Small change: can you please call self.addCleanup
> > * we need another test similar to test_status_
> > all volumes which type is not UDF are ignored.
>
> Added a volume of type "SHARE" to the fake volume_info, and it's checked to be
> ignored
> (the number of items created stays the same)
Thanks for adding this. Can you please add an assertion that checks that the SHARE id is not being loaded? because we can have the same amount of created items and yet have a buggy content in items.
Something like: self.assertNotI
I will keep reviewing later today!
Roberto Alsina (ralsina) wrote : | # |
> Great! Small change: can you please call self.addCleanup
> self.tmpdir) instead of using a lambda?
Done.
> Thanks for adding this. Can you please add an assertion that checks that the
> SHARE id is not being loaded? because we can have the same amount of created
> items and yet have a buggy content in items.
>
> Something like: self.assertNotI
Added!
Natalia Bidart (nataliabidart) wrote : | # |
* The comment added:
# Block until we have server data828944
may be misleading, since the app does not block but shows a loading message until the result from the backend is ready.
* If get_info fails, the overlay is never hid nor the user finds out that something went wrong. I filed bug #828944 so you can fix this in an incoming branch.
* test_exception_
* there are no tests for the showing/hiding of the overlay inside change_page.
* there are no tests for add_folder when the params validate, calculate and volume_id are other than the defaults. Also, having volume_id defaulting to False may be a typo? volume_id, when defined, is a uuid...
* it seems to me that update_sizes is not completely covered by tests. I think that the try-except for the RuntimeError is not tested, as well as the while True. For that last feature, we should have more than one thing being calculated its size, to show the sum of the sizes.
Also, we should confirm we're testing this case:
"if the user has no UDFs created and selects nothing" -> we should show the quota used only
and that the show_hide_offer method received the proper parameter.
* correct me if I'm wrong, but seems like there is no test for on_folder_
* question: would you agree to move all the tests for the local_folders module into its own test module, so we can follow the convention? The convention is: per every foo.py, there should be a test_foo.py.
Roberto Alsina (ralsina) wrote : | # |
> * The comment added:
>
> # Block until we have server data828944
>
> may be misleading, since the app does not block but shows a loading message
> until the result from the backend is ready.
Changed.
> * If get_info fails, the overlay is never hid nor the user finds out that
> something went wrong. I filed bug #828944 so you can fix this in an incoming
> branch.
Thanks, will look into it.
> * test_exception_
> the call does not explodes "later". In this test, when solving bug #828944,
> you should check the overlay was hidden and a message was shown to the user
> (we may need input from design here).
Agreed.
> * there are no tests for the showing/hiding of the overlay inside change_page.
Added asserts to the changed_page tests.
> * there are no tests for add_folder when the params validate, calculate and
> volume_id are other than the defaults. Also, having volume_id defaulting to
> False may be a typo? volume_id, when defined, is a uuid...
I just wanted something to say "if not volume_id". I will default it to None.
> * it seems to me that update_sizes is not completely covered by tests. I think
> that the try-except for the RuntimeError is not tested, as well as the while
> True. For that last feature, we should have more than one thing being
> calculated its size, to show the sum of the sizes.
The RuntimeError only happens when a list item is deleted by Qt before its thread
returns the result (for example, if the user leaves the page and comes back),
and it just means we can ignore that item. I am not sure how to test that, because
if I delete the items manually, it will not cause that error, unless the thread
was running while I did it. It's pretty tricky to reproduce.
The while True breaks when the Queue is empty (which will happen eventually ;-),
or any other exception occurs. You mean add a test to ensure it ends?
> Also, we should confirm we're testing this case:
>
> "if the user has no UDFs created and selects nothing" -> we should show the
> quota used only
This was covered by test_total_
and I now added test_total_
> and that the show_hide_offer method received the proper parameter.
Added a fake show_hide_offer and asserts to check that.
> * correct me if I'm wrong, but seems like there is no test for
> on_folder_
added test_on_
> nor on_add_
This was already covered by test_add_
> * question: would you agree to move all the tests for the local_folders module
> into its own test module, so we can follow the convention? The convention is:
> per every foo.py, there should be a test_foo.py.
Sure. Moved.
Natalia Bidart (nataliabidart) wrote : | # |
> > * there are no tests for add_folder when the params validate, calculate and
> > volume_id are other than the defaults. Also, having volume_id defaulting to
> > False may be a typo? volume_id, when defined, is a uuid...
>
> I just wanted something to say "if not volume_id". I will default it to None.
Makes sense.
Also, did you add tests cases for the params not having the default values?
> > * it seems to me that update_sizes is not completely covered by tests. I
> think
> > that the try-except for the RuntimeError is not tested, as well as the while
> > True. For that last feature, we should have more than one thing being
> > calculated its size, to show the sum of the sizes.
>
> The RuntimeError only happens when a list item is deleted by Qt before its
> thread
> returns the result (for example, if the user leaves the page and comes back),
> and it just means we can ignore that item. I am not sure how to test that,
> because
> if I delete the items manually, it will not cause that error, unless the
> thread
> was running while I did it. It's pretty tricky to reproduce.
You can patch item.setText, or humanize, to raise RuntimeError and check that it was handled.
> The while True breaks when the Queue is empty (which will happen eventually
> ;-),
> or any other exception occurs. You mean add a test to ensure it ends?
Nopes, I mean that if we remove the while True, I think the tests keep passing. And I think that is caused becasue no test is ensuring that the whole content of the queue is used to calculate the final size.
Thanks!
Roberto Alsina (ralsina) wrote : | # |
> > > * there are no tests for add_folder when the params validate, calculate
> and
> > > volume_id are other than the defaults. Also, having volume_id defaulting
> to
> > > False may be a typo? volume_id, when defined, is a uuid...
> >
> > I just wanted something to say "if not volume_id". I will default it to
> None.
>
> Makes sense.
>
> Also, did you add tests cases for the params not having the default values?
Yes, madam!
test_not_
test_not_
test_volume_
> > > * it seems to me that update_sizes is not completely covered by tests. I
> > think
> > > that the try-except for the RuntimeError is not tested, as well as the
> while
> > > True. For that last feature, we should have more than one thing being
> > > calculated its size, to show the sum of the sizes.
> >
> > The RuntimeError only happens when a list item is deleted by Qt before its
> > thread
> > returns the result (for example, if the user leaves the page and comes
> back),
> > and it just means we can ignore that item. I am not sure how to test that,
> > because
> > if I delete the items manually, it will not cause that error, unless the
> > thread
> > was running while I did it. It's pretty tricky to reproduce.
>
> You can patch item.setText, or humanize, to raise RuntimeError and check that
> it was handled.
Ok, will do!
> > The while True breaks when the Queue is empty (which will happen eventually
> > ;-),
> > or any other exception occurs. You mean add a test to ensure it ends?
>
> Nopes, I mean that if we remove the while True, I think the tests keep
> passing. And I think that is caused becasue no test is ensuring that the whole
> content of the queue is used to calculate the final size.
Actually, it works without the while True, it just does it slower (it may require the timer calling
the function several times). But sure, I could do a test putting some stuff in the queue and checking that it's empty at the end.
Natalia Bidart (nataliabidart) wrote : | # |
Simple lint and pep8 issues:
ubuntuone_
30: [W0611] Unused import TestCase
./ubuntuone_
./ubuntuone_
Natalia Bidart (nataliabidart) wrote : | # |
Approving lint issues are being fixed ATM.
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
Attempt to merge into lp:ubuntuone-windows-installer failed due to conflicts:
text conflict in ubuntuone_
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
Attempt to merge into lp:ubuntuone-windows-installer failed due to conflicts:
text conflict in ubuntuone_
- 65. By Roberto Alsina
-
resolve further
Preview Diff
1 | === modified file 'data/qt/local_folders.ui' |
2 | --- data/qt/local_folders.ui 2011-08-08 21:48:11 +0000 |
3 | +++ data/qt/local_folders.ui 2011-08-23 13:43:25 +0000 |
4 | @@ -97,7 +97,7 @@ |
5 | </spacer> |
6 | </item> |
7 | <item> |
8 | - <widget class="AddFolderButton" name="add_folder_button"> |
9 | + <widget class="QPushButton" name="add_folder_button"> |
10 | <property name="text"> |
11 | <string>Add a folder from this computer</string> |
12 | </property> |
13 | @@ -188,13 +188,6 @@ |
14 | </item> |
15 | </layout> |
16 | </widget> |
17 | - <customwidgets> |
18 | - <customwidget> |
19 | - <class>AddFolderButton</class> |
20 | - <extends>QPushButton</extends> |
21 | - <header>ubuntuone.controlpanel.gui.qt.addfolder</header> |
22 | - </customwidget> |
23 | - </customwidgets> |
24 | <resources/> |
25 | <connections/> |
26 | </ui> |
27 | |
28 | === modified file 'ubuntuone_installer/gui/qt/local_folders.py' |
29 | --- ubuntuone_installer/gui/qt/local_folders.py 2011-08-15 21:41:54 +0000 |
30 | +++ ubuntuone_installer/gui/qt/local_folders.py 2011-08-23 13:43:25 +0000 |
31 | @@ -24,9 +24,14 @@ |
32 | import threading |
33 | import Queue |
34 | |
35 | -from twisted.internet.defer import inlineCallbacks |
36 | +from twisted.internet.defer import inlineCallbacks, returnValue |
37 | from PyQt4 import QtCore, QtGui |
38 | -from ubuntuone.controlpanel.gui import humanize, sign_url |
39 | +from ubuntuone.controlpanel import backend |
40 | +from ubuntuone.controlpanel.gui import ( |
41 | + humanize, |
42 | + sign_url, |
43 | + FOLDER_INVALID_PATH, |
44 | +) |
45 | from ubuntuone.platform.credentials import CredentialsManagementTool |
46 | from ubuntu_sso.qt.gui import SSOWizardPage |
47 | |
48 | @@ -49,12 +54,19 @@ |
49 | |
50 | class FolderItem(QtGui.QTreeWidgetItem): |
51 | """Class representing a folder in the folder list UI.""" |
52 | - def __init__(self, strings, path=None, queue=None): |
53 | + def __init__(self, strings, path=None, queue=None, |
54 | + calculate=True, volume_id=None): |
55 | super(FolderItem, self).__init__(strings) |
56 | self.thread = CalculateSize(path, queue) |
57 | - self.thread.start() |
58 | - self.size = None |
59 | + # Don't calculate sizes of UDFs |
60 | + if calculate and not volume_id: |
61 | + self.thread.start() |
62 | + self.size = None |
63 | + else: |
64 | + self.size = 0 |
65 | self.path = path |
66 | + self.setCheckState(0, QtCore.Qt.Unchecked) |
67 | + self.volume_id = volume_id |
68 | |
69 | |
70 | class CalculateSize(threading.Thread): |
71 | @@ -83,22 +95,17 @@ |
72 | local_folders_ui.Ui_Form(), None, parent) |
73 | self.setTitle(LOCAL_FOLDERS_TITLE) |
74 | self.setSubTitle() |
75 | - |
76 | header_view = self.ui.folder_list.header() |
77 | header_view.setResizeMode(0, header_view.Stretch) |
78 | - |
79 | self.queue = Queue.Queue() |
80 | self.timer = QtCore.QTimer() |
81 | self.items = {} |
82 | - for folder_name in self.default_folders(): |
83 | - self.add_folder(folder_name) |
84 | - self.update_sizes() |
85 | - self.timer.start(2000) |
86 | - self.timer.timeout.connect(self.update_sizes) |
87 | + self.folders_info = None |
88 | + self.account_info = None |
89 | + self.cp_backend = backend.ControlBackend() |
90 | |
91 | # initializePage is inherited |
92 | # pylint: disable=C0103 |
93 | - @inlineCallbacks |
94 | def initializePage(self): |
95 | """UI details.""" |
96 | self.wizard().setButtonLayout([ |
97 | @@ -107,15 +114,75 @@ |
98 | self.wizard()._next_id = self.wizard().SYNC_NOW_OR_LATER_PAGE |
99 | # Start with this invisible |
100 | self.ui.offer_frame.setVisible(False) |
101 | - if not self.ui.folder_list.topLevelItemCount(): |
102 | + # Show overlay until we have server data |
103 | + self.wizard().overlay.show() |
104 | + self.get_info() |
105 | + |
106 | + @inlineCallbacks |
107 | + def get_info(self): |
108 | + """Get information from CP backend and fill folder list.""" |
109 | + # pylint: disable=W0702 |
110 | + try: |
111 | + volumes_info = yield self.cp_backend.volumes_info() |
112 | + self.account_info = yield self.cp_backend.account_info() |
113 | + self.folders_info = [] |
114 | + for _, _, volumes in volumes_info: |
115 | + for volume in volumes: |
116 | + if volume[u'type'] == u"UDF": |
117 | + self.folders_info.append(volume) |
118 | + self.ui.folder_list.clear() |
119 | + for folder in self.folders_info: |
120 | + item = yield self.add_folder( |
121 | + os.path.expanduser(folder['path']), |
122 | + validate=False, |
123 | + volume_id=folder['volume_id'], |
124 | + calculate=False, |
125 | + ) |
126 | + if item: |
127 | + if folder['subscribed']: |
128 | + item.setCheckState(0, QtCore.Qt.Checked) |
129 | + item.size = 0 |
130 | for folder_name in self.default_folders(): |
131 | - self.add_folder(folder_name) |
132 | - yield self.update_sizes() |
133 | + item = yield self.add_folder(folder_name, validate=True) |
134 | + self.timer.start(2000) |
135 | + self.timer.timeout.connect(self.update_sizes) |
136 | + self.wizard().overlay.hide() |
137 | + self.wizard().currentIdChanged.connect(self.changed_page) |
138 | + except: |
139 | + logger.exception("Error getting backend info:") |
140 | + |
141 | + @QtCore.pyqtSlot("int") |
142 | + @inlineCallbacks |
143 | + def changed_page(self, page_id): |
144 | + """When moving to next page, create/[un]subscribe UDFs.""" |
145 | + self.timer.stop() |
146 | + try: |
147 | + self.wizard().currentIdChanged.disconnect(self.changed_page) |
148 | + except KeyError: |
149 | + pass |
150 | + if page_id == self.wizard().SYNC_NOW_OR_LATER_PAGE: |
151 | + # The page following this one |
152 | + self.wizard().overlay.show() |
153 | + for path, item in self.items.items(): |
154 | + if item.checkState(0) == QtCore.Qt.Checked: |
155 | + if item.volume_id: |
156 | + yield self.cp_backend.change_volume_settings( |
157 | + item.volume_id, |
158 | + dict(subscribed=True)) |
159 | + else: |
160 | + yield self.cp_backend.create_folder(path) |
161 | + else: |
162 | + if item.volume_id: |
163 | + yield self.cp_backend.change_volume_settings( |
164 | + item.volume_id, |
165 | + dict(subscribed=False)) |
166 | + self.wizard().overlay.hide() |
167 | |
168 | def default_folders(self): |
169 | """Return a list of the folders to add by default.""" |
170 | if sys.platform == 'win32': |
171 | - # Special Folder "My Documents" |
172 | + # XXXX to be replaced by calls to xdg_base_directory's |
173 | + # special_folders |
174 | dll = ctypes.windll.shell32 |
175 | buf = ctypes.create_string_buffer(300) |
176 | dll.SHGetSpecialFolderPathA(None, buf, 5, False) |
177 | @@ -129,56 +196,60 @@ |
178 | result = ['To be implemented'] |
179 | return result |
180 | |
181 | - def add_folder(self, path): |
182 | + @inlineCallbacks |
183 | + def add_folder(self, path, validate=True, calculate=True, volume_id=None): |
184 | """Add a folder to the list.""" |
185 | if path in self.items: |
186 | - return None |
187 | - # FIXME: the path should actually be sent to u1cp to verify as valid |
188 | - item = FolderItem([path, "", "remove"], path=path, queue=self.queue) |
189 | - self.ui.folder_list.addTopLevelItem(item) |
190 | - self.items[path] = item |
191 | - return item |
192 | + returnValue(None) |
193 | + if validate: |
194 | + is_valid = yield self.cp_backend.validate_path_for_folder(path) |
195 | + else: |
196 | + is_valid = True |
197 | + if is_valid: |
198 | + item = FolderItem([path, ""], |
199 | + path=path, |
200 | + queue=self.queue, |
201 | + volume_id=volume_id, |
202 | + calculate=calculate) |
203 | + self.ui.folder_list.addTopLevelItem(item) |
204 | + self.items[path] = item |
205 | + returnValue(item) |
206 | + returnValue(None) |
207 | |
208 | - @inlineCallbacks |
209 | def update_sizes(self): |
210 | """Poll the queue were the threads put the size info.""" |
211 | - try: |
212 | - path, size = self.queue.get(False) |
213 | - item = self.items.get(path) |
214 | - if item: |
215 | - item.size = size |
216 | - item.setText(1, humanize(size)) |
217 | - except Queue.Empty: |
218 | - pass |
219 | - total = 0 |
220 | + while True: |
221 | + try: |
222 | + path, size = self.queue.get(False) |
223 | + except Queue.Empty: |
224 | + break |
225 | + else: |
226 | + item = self.items.get(path) |
227 | + if item: |
228 | + item.size = size |
229 | + try: |
230 | + item.setText(1, humanize(size)) |
231 | + except RuntimeError: |
232 | + del self.items[path] |
233 | + total = long(self.account_info['quota_used']) |
234 | for path, item in self.items.items(): |
235 | if item.size is None: |
236 | total = LOCAL_FOLDERS_CALCULATING |
237 | break |
238 | - total += item.size |
239 | - |
240 | + if not item.volume_id and item.checkState(0) == QtCore.Qt.Checked: |
241 | + # Existing UDFs are already accounted for, count if marked. |
242 | + total += item.size |
243 | if isinstance(total, long): |
244 | - yield self.show_hide_offer(total) |
245 | + self.show_hide_offer(total) |
246 | total = humanize(total) |
247 | else: |
248 | - yield self.show_hide_offer(0) |
249 | + self.show_hide_offer(0) |
250 | self.ui.folder_list.headerItem().setText( |
251 | 1, LOCAL_FOLDERS_SPACE_HEADER % total) |
252 | |
253 | - @inlineCallbacks |
254 | def show_hide_offer(self, cur_size): |
255 | - """Show or hide the offer to buy space according to the total size. |
256 | - |
257 | - Returns a deferred that is triggered when the update is finished. |
258 | - |
259 | - """ |
260 | - # pylint: disable=W0702 |
261 | - try: |
262 | - user_info = yield self.ui.add_folder_button.backend.account_info() |
263 | - except: |
264 | - logger.exception('Error while trying to get account info:') |
265 | - |
266 | - quota = user_info['quota_total'] |
267 | + """Show or hide the offer to buy space according to the total size.""" |
268 | + quota = self.account_info['quota_total'] |
269 | if cur_size > quota: |
270 | self.ui.offer_frame.setVisible(True) |
271 | else: |
272 | @@ -193,31 +264,44 @@ |
273 | |
274 | # itemClicked is a Qt signal name. |
275 | # pylint: disable=C0103 |
276 | - def on_folder_list_itemClicked(self, item, column): |
277 | + def on_folder_list_itemChanged(self, item, column): |
278 | """Delete folder from the list.""" |
279 | - if column == 2: |
280 | - del(self.items[item.path]) |
281 | - item.thread._stop = True |
282 | - self.ui.folder_list.takeTopLevelItem( |
283 | - self.ui.folder_list.indexOfTopLevelItem(item)) |
284 | - self.update_sizes() |
285 | + if column == 0: |
286 | + self.update_sizes() |
287 | |
288 | @inlineCallbacks |
289 | @QtCore.pyqtSlot() |
290 | def on_add_storage_button_clicked(self): |
291 | """user clicked on the "Add more storage" button.""" |
292 | - |
293 | + # Really want to catch everything |
294 | # pylint: disable=W0702 |
295 | + url = "https://one.ubuntu.com/services/#storage_panel" |
296 | try: |
297 | credtool = CredentialsManagementTool() |
298 | creds = yield credtool.find_credentials() |
299 | except: |
300 | - logger.exception('Error while trying to update que quota:') |
301 | + logger.exception('Error while trying to get credentials:') |
302 | creds = {} |
303 | - |
304 | if creds: |
305 | - signed_url = sign_url( |
306 | - "https://one.ubuntu.com/services/#storage_panel", creds) |
307 | + signed_url = sign_url(url, creds) |
308 | else: |
309 | - signed_url = "https://one.ubuntu.com/services/#storage_panel" |
310 | + signed_url = url |
311 | QtGui.QDesktopServices.openUrl(QtCore.QUrl(signed_url)) |
312 | + |
313 | + @inlineCallbacks |
314 | + @QtCore.pyqtSlot() |
315 | + def on_add_folder_button_clicked(self): |
316 | + """user clicked on the "Add Folder" button.""" |
317 | + folder = QtGui.QFileDialog.getExistingDirectory(parent=self) |
318 | + folder = unicode(folder) |
319 | + if folder == '': |
320 | + return |
321 | + |
322 | + is_valid = yield self.cp_backend.validate_path_for_folder(folder) |
323 | + if not is_valid: |
324 | + user_home = os.path.expanduser('~') |
325 | + text = FOLDER_INVALID_PATH % {'folder_path': folder, |
326 | + 'home_folder': user_home} |
327 | + QtGui.QMessageBox.warning(self, '', text, QtGui.QMessageBox.Close) |
328 | + return |
329 | + yield self.add_folder(folder, validate=False, volume_id=False) |
330 | |
331 | === modified file 'ubuntuone_installer/gui/qt/tests/test_gui.py' |
332 | --- ubuntuone_installer/gui/qt/tests/test_gui.py 2011-08-22 14:05:02 +0000 |
333 | +++ ubuntuone_installer/gui/qt/tests/test_gui.py 2011-08-23 13:43:25 +0000 |
334 | @@ -20,9 +20,6 @@ |
335 | """Tests for the Qt UI.""" |
336 | |
337 | import os |
338 | -import Queue |
339 | -import shutil |
340 | -import tempfile |
341 | |
342 | from twisted.internet import defer |
343 | from PyQt4 import QtGui, QtCore |
344 | @@ -32,7 +29,6 @@ |
345 | from ubuntuone_installer.gui.qt import gui |
346 | from ubuntuone_installer.gui.qt.tests import BaseTestCase |
347 | from ubuntuone_installer.gui.qt import forgotten |
348 | -from ubuntuone_installer.gui.qt import local_folders |
349 | from ubuntuone_installer.gui.qt import setup_account |
350 | from ubuntuone_installer.gui.qt.ui import ( |
351 | setup_account_ui, |
352 | @@ -217,6 +213,7 @@ |
353 | self.ui.restart() |
354 | self.ui.show() |
355 | |
356 | + # pylint: disable=W0212 |
357 | preferences_page._button_clicked(QtGui.QWizard.CustomButton2) |
358 | self.assertEqual( |
359 | preferences_page.preferences_widget.restore_button_clicked, True) |
360 | @@ -245,6 +242,7 @@ |
361 | self.assertEqual(tree_folders.isHeaderHidden(), True) |
362 | tree_folders = folders_page.folders_widget.ui.folders |
363 | self.assertEqual(tree_folders.isColumnHidden(2), True) |
364 | + # pylint: disable=W0212 |
365 | self.assertEqual(self.ui._next_id, self.ui.CONGRATULATIONS_PAGE) |
366 | |
367 | def test_folders_page_next_id(self): |
368 | @@ -571,120 +569,19 @@ |
369 | self.shown = True |
370 | |
371 | |
372 | -class FakeCPBackend(object): |
373 | - """Fake Control Panel backend.""" |
374 | - |
375 | - def account_info(self, *args): |
376 | - """Fake account info.""" |
377 | - return defer.succeed({"quota_total": 1000}) |
378 | - |
379 | - |
380 | -class FakeAddFolderButton(object): |
381 | - """Fake Control Panel "Add Folder" button.""" |
382 | - backend = FakeCPBackend() |
383 | - |
384 | - |
385 | -class LocalFoldersTestCase(BaseTestCase): |
386 | - """Test the LocalFoldersPage code.""" |
387 | - |
388 | - class_ui = local_folders.LocalFoldersPage |
389 | - |
390 | - def setUp(self): |
391 | - """Initialize this test instance.""" |
392 | - # Create a test folder with a known size |
393 | - self.tmpdir = tempfile.mkdtemp() |
394 | - f = open(os.path.join(self.tmpdir, 'test_file'), 'wb') |
395 | - f.write(" " * 600) |
396 | - f.close() |
397 | - os.mkdir(os.path.join(self.tmpdir, 'test_dir')) |
398 | - f = open(os.path.join(self.tmpdir, 'test_dir', 'test_file_2'), 'wb') |
399 | - f.write(" " * 737) |
400 | - f.close() |
401 | - super(LocalFoldersTestCase, self).setUp() |
402 | - |
403 | - def tearDown(self): |
404 | - """Remove the temporary tree.""" |
405 | - shutil.rmtree(self.tmpdir) |
406 | - BaseTestCase.tearDown(self) |
407 | - |
408 | - def test_size_calculation(self): |
409 | - """Test the recursive folder size calculation.""" |
410 | - queue = Queue.Queue() |
411 | - csize = local_folders.CalculateSize(self.tmpdir, queue) |
412 | - csize.run() |
413 | - path, size = queue.get() |
414 | - self.assertEqual(path, self.tmpdir) |
415 | - self.assertEqual(size, 1337) |
416 | - |
417 | - def test_item_addition(self): |
418 | - """Add an item (plus the default one), then remove them.""" |
419 | - self.ui.add_folder(self.tmpdir) |
420 | - self.assertEqual(4, self.ui.ui.folder_list.topLevelItemCount()) |
421 | - |
422 | - def test_total_size(self): |
423 | - """Test that the header reflects the change in item sizes.""" |
424 | - while self.ui.ui.folder_list.topLevelItemCount(): |
425 | - self.ui.on_folder_list_itemClicked( |
426 | - self.ui.ui.folder_list.topLevelItem(0), 2) |
427 | - self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) |
428 | - item = self.ui.add_folder(self.tmpdir) |
429 | - item.size = 1337 |
430 | - item.thread.run() |
431 | - item.thread.join() |
432 | - self.ui.update_sizes() |
433 | - self.assertEqual(unicode(self.ui.ui.folder_list.headerItem().text(1)), |
434 | - u"Space (1337)") |
435 | - |
436 | - def test_add_twice(self): |
437 | - """Behaviour for adding the same folder twice. |
438 | - |
439 | - * It's added only once. |
440 | - """ |
441 | - while self.ui.ui.folder_list.topLevelItemCount(): |
442 | - self.ui.on_folder_list_itemClicked( |
443 | - self.ui.ui.folder_list.topLevelItem(0), 2) |
444 | - self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) |
445 | - self.ui.add_folder(self.tmpdir) |
446 | - self.ui.add_folder(self.tmpdir) |
447 | - self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
448 | - |
449 | - def test_add_missing_folder(self): |
450 | - """Behaviour for adding a folder that doesn't exist. |
451 | - |
452 | - * It's added. |
453 | - * Has size 0. |
454 | - """ |
455 | - |
456 | - while self.ui.ui.folder_list.topLevelItemCount(): |
457 | - self.ui.on_folder_list_itemClicked( |
458 | - self.ui.ui.folder_list.topLevelItem(0), 2) |
459 | - self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) |
460 | - item = self.ui.add_folder(os.path.join("xyzzy", "xyzzy", "xyzzy")) |
461 | - self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
462 | - item.thread.run() |
463 | - item.thread.join() |
464 | - self.ui.update_sizes() |
465 | - self.assertEqual(0, item.size) |
466 | - # world did not explode |
467 | - |
468 | - def test_over_quota(self): |
469 | - """After removing all folders, offer_frame should be hidden. |
470 | - |
471 | - Push the user over quota, it should be visible. |
472 | - """ |
473 | - self.patch(self.ui.ui.offer_frame, "setVisible", self._set_called) |
474 | - while self.ui.ui.folder_list.topLevelItemCount(): |
475 | - self.ui.on_folder_list_itemClicked( |
476 | - self.ui.ui.folder_list.topLevelItem(0), 2) |
477 | - self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) |
478 | - self.ui.update_sizes() |
479 | - self.assertEqual(self._called, ((False,), {})) |
480 | - self.ui.show_hide_offer(self.ui.quota() + 1) |
481 | - self.assertEqual(self._called, ((True,), {})) |
482 | - |
483 | - |
484 | -LocalFoldersTestCase.skip = 'We need to re-write this test case entirely,' \ |
485 | - 'see bug #824675 for reference.' |
486 | +class FakeWizard(object): |
487 | + """Replace wizard() function on wizard pages.""" |
488 | + |
489 | + params = None |
490 | + |
491 | + # Invalid name "setButtonLayout" |
492 | + # pylint: disable=C0103 |
493 | + |
494 | + def setButtonLayout(self, *args, **kwargs): |
495 | + """Fake the functionality of setButtonLayout on QWizard class.""" |
496 | + FakeWizard.params = (args, kwargs) |
497 | + |
498 | + # pylint: enable=C0103 |
499 | |
500 | |
501 | class FakeWizard(object): |
502 | |
503 | === added file 'ubuntuone_installer/gui/qt/tests/test_local_folders.py' |
504 | --- ubuntuone_installer/gui/qt/tests/test_local_folders.py 1970-01-01 00:00:00 +0000 |
505 | +++ ubuntuone_installer/gui/qt/tests/test_local_folders.py 2011-08-23 13:43:25 +0000 |
506 | @@ -0,0 +1,585 @@ |
507 | +# -*- coding: utf-8 -*- |
508 | + |
509 | +# Authors: Roberto Alsina <roberto.alsina@canonical.com> |
510 | +# |
511 | +# Copyright 2011 Canonical Ltd. |
512 | +# |
513 | +# This program is free software: you can redistribute it and/or modify it |
514 | +# under the terms of the GNU General Public License version 3, as published |
515 | +# by the Free Software Foundation. |
516 | +# |
517 | +# This program is distributed in the hope that it will be useful, but |
518 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
519 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
520 | +# PURPOSE. See the GNU General Public License for more details. |
521 | +# |
522 | +# You should have received a copy of the GNU General Public License along |
523 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
524 | + |
525 | +"""Tests for the local folders page.""" |
526 | + |
527 | +import os |
528 | +import Queue |
529 | +import shutil |
530 | +import tempfile |
531 | + |
532 | +from twisted.internet import defer |
533 | +from PyQt4 import QtGui, QtCore |
534 | + |
535 | +from ubuntuone_installer.gui.qt import local_folders |
536 | +from ubuntuone_installer.gui.qt.tests import BaseTestCase, TestCase |
537 | +from ubuntuone_installer.gui.qt.tests.test_gui import FakeOverlay |
538 | + |
539 | + |
540 | +class FakeFileDialog(object): |
541 | + |
542 | + """A fake QFileDialog class.""" |
543 | + |
544 | + # pylint: disable=C0103 |
545 | + def getExistingDirectory(self, *args, **kwargs): |
546 | + """Fake existing folder name.""" |
547 | + return u"whatever" |
548 | + |
549 | + |
550 | +class FakeMessageBox(object): |
551 | + |
552 | + """A fake QMessageBox class.""" |
553 | + |
554 | + Close = 2 |
555 | + _warning = None |
556 | + |
557 | + def warning(self, *args, **kwargs): |
558 | + """Fake warning.""" |
559 | + self._warning = (args, kwargs) |
560 | + |
561 | + |
562 | +class FakeCPBackend(object): |
563 | + """Fake Control Panel backend.""" |
564 | + |
565 | + def __init__(self): |
566 | + """Initialize.""" |
567 | + self._is_valid = True |
568 | + self.volume_setings_changes = [] |
569 | + self.folders_created = [] |
570 | + |
571 | + def account_info(self, *args): |
572 | + """Fake account info.""" |
573 | + return defer.succeed({ |
574 | + "quota_total": 1000, |
575 | + "quota_used": 200, |
576 | + }) |
577 | + |
578 | + def volumes_info(self, *args): |
579 | + """Fake volumes info.""" |
580 | + return defer.succeed(((None, None, |
581 | + [ |
582 | + { |
583 | + 'type': u'UDF', |
584 | + 'path': os.path.expanduser(u'~/xyzzy'), |
585 | + 'volume_id': 'asdfgh', |
586 | + 'subscribed': True, |
587 | + }, |
588 | + { |
589 | + 'type': u'UDF', |
590 | + 'path': os.path.expanduser(u'~/zxyzzy'), |
591 | + 'volume_id': 'qwerty', |
592 | + 'subscribed': False, |
593 | + }, |
594 | + { |
595 | + 'type': u'SHARE', |
596 | + 'path': os.path.expanduser(u'~/foobar'), |
597 | + 'volume_id': 'shared', |
598 | + 'subscribed': False, |
599 | + }, |
600 | + ], |
601 | + ),)) |
602 | + |
603 | + def validate_path_for_folder(self, path): |
604 | + """Fake folder validation.""" |
605 | + return self._is_valid |
606 | + |
607 | + def change_volume_settings(self, *args): |
608 | + """Fake change volume settings.""" |
609 | + self.volume_setings_changes.append(args) |
610 | + |
611 | + def create_folder(self, *args): |
612 | + """Fake folder creation.""" |
613 | + self.folders_created.append(args) |
614 | + |
615 | + |
616 | +class FakeFailingCPBackend(object): |
617 | + """Fake Control Panel backend that fails.""" |
618 | + |
619 | + def account_info(self, *args): |
620 | + """Fake account info.""" |
621 | + return defer.fail(Exception()) |
622 | + |
623 | + def volumes_info(self, *args): |
624 | + """Fake account info.""" |
625 | + return defer.fail(Exception()) |
626 | + |
627 | + |
628 | +class FakeSignal(object): |
629 | + |
630 | + """A fake PyQt signal.""" |
631 | + |
632 | + def __init__(self, *args, **kwargs): |
633 | + """Initialize.""" |
634 | + self.target = None |
635 | + |
636 | + def connect(self, target): |
637 | + """Fake connect.""" |
638 | + self.target = target |
639 | + |
640 | + def disconnect(self, *args): |
641 | + """Fake disconnect.""" |
642 | + self.target = None |
643 | + |
644 | + def emit(self, *args): |
645 | + """Fake emit.""" |
646 | + if self.target: |
647 | + self.target(*args) |
648 | + |
649 | + |
650 | +class FakeMainWindow(object): |
651 | + |
652 | + """A fake MainWindow.""" |
653 | + |
654 | + loginSuccess = FakeSignal() |
655 | + registrationSuccess = FakeSignal() |
656 | + userCancellation = FakeSignal() |
657 | + shown = False |
658 | + _buttonlayout = None |
659 | + SYNC_NOW_OR_LATER_PAGE = 4 |
660 | + overlay = FakeOverlay() |
661 | + currentIdChanged = FakeSignal() |
662 | + |
663 | + def show(self): |
664 | + """Fake method.""" |
665 | + self.shown = True |
666 | + |
667 | + # pylint: disable=C0103 |
668 | + def setButtonLayout(self, *args): |
669 | + """Save ButtonLayout.""" |
670 | + self._buttonlayout = args |
671 | + |
672 | + |
673 | +class FakeCalculateSize(object): |
674 | + |
675 | + """A fake CalculateSize.""" |
676 | + |
677 | + def __init__(self, *args, **kwargs): |
678 | + self.started = False |
679 | + |
680 | + def start(self): |
681 | + """Fake start.""" |
682 | + self.started = True |
683 | + |
684 | + |
685 | +class FolderItemTestCase(TestCase): |
686 | + |
687 | + """Tests for the FolderItem class.""" |
688 | + |
689 | + def test_folder_item_new_udf(self): |
690 | + """Test when created like a new UDF.""" |
691 | + self.patch(local_folders, "CalculateSize", FakeCalculateSize) |
692 | + item = local_folders.FolderItem(["s1", "s2"], "path") |
693 | + self.assertTrue(item.size is None) |
694 | + self.assertEqual(item.path, "path") |
695 | + # pylint: disable=E1101 |
696 | + self.assertTrue(item.thread.started) |
697 | + self.assertEqual(item.checkState(0), QtCore.Qt.Unchecked) |
698 | + self.assertTrue(item.volume_id is None) |
699 | + |
700 | + def test_folder_item_existing_udf(self): |
701 | + """Test when created like an existing UDF.""" |
702 | + self.patch(local_folders, "CalculateSize", FakeCalculateSize) |
703 | + item = local_folders.FolderItem(["s1", "s2"], "path", |
704 | + calculate=False, volume_id="xyzzy") |
705 | + self.assertEqual(item.size, 0) |
706 | + self.assertEqual(item.path, "path") |
707 | + # pylint: disable=E1101 |
708 | + self.assertFalse(item.thread.started) |
709 | + self.assertEqual(item.checkState(0), QtCore.Qt.Unchecked) |
710 | + self.assertEqual(item.volume_id, "xyzzy") |
711 | + |
712 | + |
713 | +class LocalFoldersTestCase(BaseTestCase): |
714 | + """Test the LocalFoldersPage code.""" |
715 | + |
716 | + class_ui = local_folders.LocalFoldersPage |
717 | + |
718 | + def setUp(self): |
719 | + """Initialize this test instance.""" |
720 | + # Create a test folder with a known size |
721 | + self.tmpdir = tempfile.mkdtemp() |
722 | + f = open(os.path.join(self.tmpdir, 'test_file'), 'wb') |
723 | + f.write(" " * 600) |
724 | + f.close() |
725 | + os.mkdir(os.path.join(self.tmpdir, 'test_dir')) |
726 | + f = open(os.path.join(self.tmpdir, 'test_dir', 'test_file_2'), 'wb') |
727 | + f.write(" " * 737) |
728 | + f.close() |
729 | + self.addCleanup(shutil.rmtree, self.tmpdir) |
730 | + self.fake_wizard = FakeMainWindow() |
731 | + self.patch(local_folders.backend, "ControlBackend", FakeCPBackend) |
732 | + super(LocalFoldersTestCase, self).setUp() |
733 | + self.patch(self.ui, "wizard", lambda: self.fake_wizard) |
734 | + |
735 | + def test_status_before_initialize(self): |
736 | + """Test status of the page components before initializePage().""" |
737 | + self.assertIsInstance(self.ui.timer, QtCore.QTimer) |
738 | + self.assertFalse(self.ui.timer.isActive()) |
739 | + self.assertTrue(self.ui.folders_info is None) |
740 | + self.assertTrue(self.ui.account_info is None) |
741 | + self.assertEqual(self.ui.items, {}) |
742 | + self.assertEqual(self.ui.ui.folder_list.topLevelItemCount(), 0) |
743 | + |
744 | + def test_status_after_initialize(self): |
745 | + """Test status of page components after initializePage().""" |
746 | + self.patch(self.ui, "get_info", self._set_called) |
747 | + self.ui.initializePage() |
748 | + self.assertTrue(self.ui.wizard().overlay.show_counter, 1) |
749 | + self.assertTrue(self.ui.wizard().overlay.hide_counter, 0) |
750 | + self.assertFalse(self.ui.ui.offer_frame.isVisible()) |
751 | + self.assertEqual(self._called, ((), {})) |
752 | + |
753 | + @defer.inlineCallbacks |
754 | + def test_status_after_get_info(self): |
755 | + """Test status of page components after get_info().""" |
756 | + yield self.ui.get_info() |
757 | + self.assertTrue(self.ui.wizard().overlay.hide_counter, 1) |
758 | + self.assertEqual(self.ui.ui.folder_list.topLevelItemCount(), 5) |
759 | + self.assertEqual(self.ui.wizard().currentIdChanged.target, |
760 | + self.ui.changed_page) |
761 | + self.assertEqual( |
762 | + self.ui.items[os.path.expanduser('~/xyzzy')].checkState(0), |
763 | + QtCore.Qt.Checked) |
764 | + self.assertEqual( |
765 | + self.ui.items[os.path.expanduser('~/zxyzzy')].checkState(0), |
766 | + QtCore.Qt.Unchecked) |
767 | + check_states = [self.ui.items[item].checkState(0) |
768 | + for item in self.ui.items] |
769 | + self.assertEqual(len([ |
770 | + x for x in check_states if x != QtCore.Qt.Unchecked]), 1) |
771 | + self.assertNotIn('shared', self.ui.items) |
772 | + |
773 | + @defer.inlineCallbacks |
774 | + def test_subscribed_udf_checked(self): |
775 | + """Check that subscribed UDF items are created correctly.""" |
776 | + yield self.ui.get_info() |
777 | + item = self.ui.items[os.path.expanduser(u'~/xyzzy')] |
778 | + self.assertEqual(item.checkState(0), QtCore.Qt.Checked) |
779 | + self.assertEqual(item.size, 0) |
780 | + self.assertEqual(item.volume_id, 'asdfgh') |
781 | + |
782 | + @defer.inlineCallbacks |
783 | + def test_unsubscribed_udf_checked(self): |
784 | + """Check that unsubscribed UDF items are created correctly.""" |
785 | + yield self.ui.get_info() |
786 | + item = self.ui.items[os.path.expanduser(u'~/zxyzzy')] |
787 | + self.assertEqual(item.checkState(0), QtCore.Qt.Unchecked) |
788 | + self.assertEqual(item.size, 0) |
789 | + self.assertEqual(item.volume_id, 'qwerty') |
790 | + |
791 | + def test_size_calculation(self): |
792 | + """Test the recursive folder size calculation.""" |
793 | + queue = Queue.Queue() |
794 | + csize = local_folders.CalculateSize(self.tmpdir, queue) |
795 | + csize.run() |
796 | + path, size = queue.get() |
797 | + self.assertEqual(path, self.tmpdir) |
798 | + self.assertEqual(size, 1337) |
799 | + |
800 | + @defer.inlineCallbacks |
801 | + def test_item_addition(self): |
802 | + """Add a folder.""" |
803 | + self.ui.ui.folder_list.clear() |
804 | + yield self.ui.add_folder(self.tmpdir) |
805 | + self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
806 | + |
807 | + @defer.inlineCallbacks |
808 | + def test_invalid_item_addition(self): |
809 | + """Try to add an invalid folder.""" |
810 | + self.ui.ui.folder_list.clear() |
811 | + self.ui.cp_backend._is_valid = False |
812 | + yield self.ui.add_folder(self.tmpdir) |
813 | + self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) |
814 | + |
815 | + @defer.inlineCallbacks |
816 | + def test_not_validating_invalid_item_addition(self): |
817 | + """Try to add an invalid folder, and not validate it.""" |
818 | + self.ui.ui.folder_list.clear() |
819 | + self.ui.cp_backend._is_valid = False |
820 | + yield self.ui.add_folder(self.tmpdir, validate=False) |
821 | + self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
822 | + |
823 | + @defer.inlineCallbacks |
824 | + def test_not_calculating_item_addition(self): |
825 | + """Try to add a folder, and not calculate it.""" |
826 | + self.ui.ui.folder_list.clear() |
827 | + yield self.ui.add_folder(self.tmpdir, calculate=False) |
828 | + self.assertEqual(self.ui.items[self.tmpdir].size, 0) |
829 | + self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
830 | + |
831 | + @defer.inlineCallbacks |
832 | + def test_volume_id_item_addition(self): |
833 | + """Try to add a folder with a volume_id.""" |
834 | + self.ui.ui.folder_list.clear() |
835 | + yield self.ui.add_folder(self.tmpdir, volume_id="vol") |
836 | + self.assertEqual(self.ui.items[self.tmpdir].volume_id, "vol") |
837 | + self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
838 | + |
839 | + @defer.inlineCallbacks |
840 | + def test_total_size(self): |
841 | + """Test that the header reflects the change in item sizes.""" |
842 | + self.patch(self.ui, "show_hide_offer", self._set_called) |
843 | + yield self.ui.get_info() |
844 | + self.ui.ui.folder_list.clear() |
845 | + self.ui.items = {} |
846 | + item = yield self.ui.add_folder(self.tmpdir) |
847 | + item.thread.run() |
848 | + item.thread.join() |
849 | + item.size = 1337 |
850 | + item.setCheckState(0, QtCore.Qt.Checked) |
851 | + self.ui.update_sizes() |
852 | + self.assertEqual(unicode(self.ui.ui.folder_list.headerItem().text(1)), |
853 | + u"Space (1.5 KiB)") |
854 | + self.assertEqual(self._called, ((1537L,), {})) |
855 | + |
856 | + @defer.inlineCallbacks |
857 | + def test_total_size_unchecked(self): |
858 | + """Unchecked items use no space beyond quota_used.""" |
859 | + self.patch(self.ui, "show_hide_offer", self._set_called) |
860 | + yield self.ui.get_info() |
861 | + self.ui.ui.folder_list.clear() |
862 | + self.ui.items = {} |
863 | + item = yield self.ui.add_folder(self.tmpdir) |
864 | + item.thread.run() |
865 | + item.thread.join() |
866 | + item.size = 1337 |
867 | + item.setCheckState(0, QtCore.Qt.Unchecked) |
868 | + self.ui.update_sizes() |
869 | + self.assertEqual(unicode(self.ui.ui.folder_list.headerItem().text(1)), |
870 | + u"Space (200 bytes)") |
871 | + self.assertEqual(self._called, ((200L,), {})) |
872 | + |
873 | + @defer.inlineCallbacks |
874 | + def test_total_size_unchecked_no_udfs(self): |
875 | + """No checked items and no quota used should amount to 0 bytes.""" |
876 | + self.patch(self.ui, "show_hide_offer", self._set_called) |
877 | + yield self.ui.get_info() |
878 | + self.ui.account_info['quota_used'] = 0 |
879 | + self.ui.ui.folder_list.clear() |
880 | + self.ui.items = {} |
881 | + item = yield self.ui.add_folder(self.tmpdir) |
882 | + item.thread.run() |
883 | + item.thread.join() |
884 | + item.size = 1337 |
885 | + item.setCheckState(0, QtCore.Qt.Unchecked) |
886 | + self.ui.update_sizes() |
887 | + self.assertEqual(unicode(self.ui.ui.folder_list.headerItem().text(1)), |
888 | + u"Space (0 bytes)") |
889 | + self.assertEqual(self._called, ((0,), {})) |
890 | + |
891 | + @defer.inlineCallbacks |
892 | + def test_total_size_udf(self): |
893 | + """UDFs use no space beyond quota_used.""" |
894 | + self.patch(self.ui, "show_hide_offer", self._set_called) |
895 | + yield self.ui.get_info() |
896 | + for _, item in self.ui.items.items(): |
897 | + if item.thread.is_alive(): |
898 | + item.thread.join() |
899 | + if item.volume_id: |
900 | + item.setCheckState(0, QtCore.Qt.Checked) |
901 | + else: |
902 | + item.setCheckState(0, QtCore.Qt.Unchecked) |
903 | + self.ui.update_sizes() |
904 | + self.assertEqual(unicode(self.ui.ui.folder_list.headerItem().text(1)), |
905 | + u"Space (200 bytes)") |
906 | + self.assertEqual(self._called, ((200L,), {})) |
907 | + |
908 | + def test_add_twice(self): |
909 | + """Behaviour for adding the same folder twice: |
910 | + |
911 | + * It's added only once. |
912 | + """ |
913 | + self.ui.ui.folder_list.clear() |
914 | + self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) |
915 | + self.ui.add_folder(self.tmpdir) |
916 | + self.ui.add_folder(self.tmpdir) |
917 | + self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
918 | + |
919 | + @defer.inlineCallbacks |
920 | + def test_add_missing_folder(self): |
921 | + """Behaviour for adding a folder that doesn't exist: |
922 | + |
923 | + * It's added. |
924 | + * Has size 0. |
925 | + """ |
926 | + |
927 | + yield self.ui.get_info() |
928 | + self.ui.ui.folder_list.clear() |
929 | + self.assertEqual(0, self.ui.ui.folder_list.topLevelItemCount()) |
930 | + item = yield self.ui.add_folder(os.path.join( |
931 | + "xyzzy", "xyzzy", "xyzzy")) |
932 | + self.assertEqual(1, self.ui.ui.folder_list.topLevelItemCount()) |
933 | + item.thread.run() |
934 | + item.thread.join() |
935 | + self.ui.update_sizes() |
936 | + self.assertEqual(0, item.size) |
937 | + # world did not explode |
938 | + |
939 | + def test_over_quota(self): |
940 | + """After removing all folders, offer_frame should be hidden. |
941 | + |
942 | + Push the user over quota, it should be visible. |
943 | + """ |
944 | + self.patch(self.ui.ui.offer_frame, "setVisible", self._set_called) |
945 | + yield self.ui.get_info() |
946 | + self.ui.ui.folder_list.clear() |
947 | + self.ui.update_sizes() |
948 | + self.assertEqual(self._called, ((False,), {})) |
949 | + self.ui.show_hide_offer(self.ui.quota() + 1) |
950 | + self.assertEqual(self._called, ((True,), {})) |
951 | + |
952 | + def test_add_folder_clicked_valid(self): |
953 | + """Test behaviour when adding a valid folder via the button.""" |
954 | + self.patch(QtGui, "QFileDialog", FakeFileDialog()) |
955 | + self.patch(self.ui, "add_folder", self._set_called) |
956 | + self.ui.ui.add_folder_button.click() |
957 | + self.assertEqual(self._called, |
958 | + ((u'whatever',), {'validate': False, 'volume_id': False})) |
959 | + |
960 | + def test_add_folder_clicked_invalid(self): |
961 | + """Test behaviour when adding an invalid folder via the button.""" |
962 | + self.patch(QtGui, "QFileDialog", FakeFileDialog()) |
963 | + message_box = FakeMessageBox() |
964 | + self.patch(QtGui, "QMessageBox", message_box) |
965 | + self.patch(self.ui, "add_folder", self._set_called) |
966 | + self.ui.cp_backend._is_valid = False |
967 | + self.ui.ui.add_folder_button.click() |
968 | + self.assertEqual(self._called, False) |
969 | + user_home = os.path.expanduser('~') |
970 | + text = local_folders.FOLDER_INVALID_PATH % { |
971 | + 'folder_path': "whatever", |
972 | + 'home_folder': user_home, |
973 | + } |
974 | + # pylint: disable=W0212 |
975 | + self.assertEqual(message_box._warning, |
976 | + ((self.ui, |
977 | + '', text, 2), {})) |
978 | + |
979 | + @defer.inlineCallbacks |
980 | + def test_changed_page_existing_udf_behaviour(self): |
981 | + """If a UDF is checked, subscribe it, if not, unsubscribe it.""" |
982 | + yield self.ui.get_info() |
983 | + show_counter = self.ui.wizard().overlay.show_counter |
984 | + hide_counter = self.ui.wizard().overlay.hide_counter |
985 | + self.ui.changed_page(self.ui.wizard().SYNC_NOW_OR_LATER_PAGE) |
986 | + self.assertEqual(self.ui.cp_backend.volume_setings_changes, |
987 | + [('asdfgh', {'subscribed': True}), |
988 | + ('qwerty', {'subscribed': False})]) |
989 | + self.assertEqual(self.ui.wizard().overlay.show_counter, |
990 | + show_counter + 1) |
991 | + self.assertEqual(self.ui.wizard().overlay.hide_counter, |
992 | + hide_counter + 1) |
993 | + |
994 | + @defer.inlineCallbacks |
995 | + def test_changed_page_new_udf_behaviour(self): |
996 | + """Create UDFs for non-existing, checked UDFs.""" |
997 | + yield self.ui.get_info() |
998 | + show_counter = self.ui.wizard().overlay.show_counter |
999 | + hide_counter = self.ui.wizard().overlay.hide_counter |
1000 | + self.ui.ui.folder_list.clear() |
1001 | + self.ui.items = {} |
1002 | + item = yield self.ui.add_folder("whatever") |
1003 | + item.setCheckState(0, QtCore.Qt.Checked) |
1004 | + item = yield self.ui.add_folder("whatever2") |
1005 | + item.setCheckState(0, QtCore.Qt.Unchecked) |
1006 | + self.ui.changed_page(self.ui.wizard().SYNC_NOW_OR_LATER_PAGE) |
1007 | + self.assertEqual(self.ui.cp_backend.folders_created, |
1008 | + [('whatever',)]) |
1009 | + self.assertEqual(self.ui.wizard().overlay.show_counter, |
1010 | + show_counter + 1) |
1011 | + self.assertEqual(self.ui.wizard().overlay.hide_counter, |
1012 | + hide_counter + 1) |
1013 | + |
1014 | + def test_exception_on_account_info(self): |
1015 | + """When account_info fails, nothing should happen.""" |
1016 | + self.patch(self.ui, |
1017 | + "cp_backend", FakeFailingCPBackend()) |
1018 | + self.ui.get_info() |
1019 | + # Still here |
1020 | + |
1021 | + def test_timer_is_started(self): |
1022 | + """When displaying the page, the timer should start.""" |
1023 | + self.ui.initializePage() |
1024 | + self.assertTrue(self.ui.timer.isActive()) |
1025 | + |
1026 | + def test_timer_is_stopped(self): |
1027 | + """When leaving the page, the timer should stop.""" |
1028 | + self.ui.changed_page(-1) |
1029 | + self.assertFalse(self.ui.timer.isActive()) |
1030 | + |
1031 | + def test_special_folders(self): |
1032 | + """Test that default_folders does the right thing.""" |
1033 | + folders = self.ui.default_folders() |
1034 | + self.assertEqual(len(folders), 3) |
1035 | + self.assertEqual(folders, [os.path.abspath(f) for f in folders]) |
1036 | + # pylint: disable=W0404 |
1037 | + import ctypes |
1038 | + dll = ctypes.windll.shell32 |
1039 | + buf = ctypes.create_string_buffer(300) |
1040 | + dll.SHGetSpecialFolderPathA(None, buf, 5, False) |
1041 | + docs = buf.value |
1042 | + dll.SHGetSpecialFolderPathA(None, buf, 13, False) |
1043 | + music = buf.value |
1044 | + dll.SHGetSpecialFolderPathA(None, buf, 39, False) |
1045 | + pictures = buf.value |
1046 | + expected = [docs, music, pictures] |
1047 | + self.assertEqual(sorted(folders), sorted(expected)) |
1048 | + |
1049 | + def test_on_folder_list_item_changed_col_0(self): |
1050 | + """Test that it calls the right thing.""" |
1051 | + self.patch(self.ui, 'update_sizes', self._set_called) |
1052 | + self.ui.on_folder_list_itemChanged("item", 0) |
1053 | + self.assertEqual(self._called, ((), {})) |
1054 | + |
1055 | + def test_on_folder_list_item_changed_col_1(self): |
1056 | + """Test that it calls nothing.""" |
1057 | + self.patch(self.ui, 'update_sizes', self._set_called) |
1058 | + self.ui.on_folder_list_itemChanged("item", 1) |
1059 | + self.assertFalse(self._called) |
1060 | + |
1061 | + @defer.inlineCallbacks |
1062 | + def test_runtime_error(self): |
1063 | + """Test that runtime error is handled correctly.""" |
1064 | + |
1065 | + def raise_runtime(*args): |
1066 | + """Dummy function that raises RuntimeError.""" |
1067 | + raise RuntimeError |
1068 | + |
1069 | + yield self.ui.get_info() |
1070 | + item = yield self.ui.add_folder( |
1071 | + "dummy", validate=False, calculate=False) |
1072 | + self.patch(item, 'setText', raise_runtime) |
1073 | + self.ui.queue.put(["dummy", 1000]) |
1074 | + self.assertIn("dummy", self.ui.items) |
1075 | + self.ui.update_sizes() |
1076 | + self.assertNotIn("dummy", self.ui.items) |
1077 | + |
1078 | + @defer.inlineCallbacks |
1079 | + def test_queue_is_emptied(self): |
1080 | + """Test that update_sizes empties the queue.""" |
1081 | + self.ui.account_info = { |
1082 | + "quota_total": 1000, |
1083 | + "quota_used": 200, |
1084 | + } |
1085 | + yield self.ui.add_folder("dummy1", validate=False, calculate=False) |
1086 | + yield self.ui.add_folder("dummy2", validate=False, calculate=False) |
1087 | + self.ui.queue.put(["dummy1", 1000]) |
1088 | + self.ui.queue.put(["dummy2", 1000]) |
1089 | + self.assertFalse(self.ui.queue.empty()) |
1090 | + self.ui.update_sizes() |
1091 | + self.assertTrue(self.ui.queue.empty()) |
* We should add a new test case for FolderItem in particular, asserting at least the following:
- the checkstate is unchecked
- the volume id is the one passed as parameter
- the thread is started if calculate is True, not started if not
- size is None if calculate, 0 if not
* Also, I would advice moving the self.thread assignment outside the if-else since is the same one no matter the value of calculate
* 4 double quotes instead of 3 in:
404 + """"Fake volumes info."""
* Always call super() and not the parent class directly. So, in this case:
475 def tearDown(self): rmtree( self.tmpdir) tearDown( self)
476 """Remove the temporary tree."""
477 shutil.
478 BaseTestCase.
we should call super(). But, considering we can move the rmtree to the setUp method as a cleanup function (which is preferred than using tearDown), we can remove this tearDown altogether.
* in test_status_ after_initializ e, these two assertions:
will likely fail on slow machines, since get_info() returns a deferred which initializePage() is not waiting for. So, I would advice converting initializePage into an asynchronous method and yielding on this in the test. Or, alternatively, since you are already providing a test for get_info, you can just assert get_info was called, and remove the checks for volume_info, account_info, items, timer, etc. after_initializ e should only check:
Basically, test_status_
- self.ui.offer_frame is not visible ).overlay is visible
- self.wizard(
- get_info was called
* we need another test similar to test_status_ post_get_ info but testing that all volumes which type is not UDF are ignored.
* I'm confused about the call to item.thread.join() in get_info. Can you please help me understand why is needed at that point?
* this is not correct: self.assertTrue (item.volume_ id). Since volume_id is not a boolean, we should assert equality against the value used when mocking volumes_info.
* test_size_ calculation is creating a thread when calling CalculateSize but I think that thread is not being joined on. If so, can you please fix that?
* are there tests for change_page? I see one for the timer being stopped, but we need some for the try-except clause, and all the block right after the page_id == self.wizard( ).SYNC_ NOW_OR_ LATER_PAGE guard. Also, never have errors passing silently, please add a log message when handling the KeyError in self.wizard( ).currentIdChan ged.disconnect( ).
I will continue the review once these are fixed.