Merge lp:~mandel/ubuntuone-client/improve-fs-notifications-logging into lp:ubuntuone-client

Proposed by Manuel de la Peña
Status: Merged
Approved by: Manuel de la Peña
Approved revision: 1155
Merged at revision: 1151
Proposed branch: lp:~mandel/ubuntuone-client/improve-fs-notifications-logging
Merge into: lp:ubuntuone-client
Diff against target: 259 lines (+90/-6)
2 files modified
tests/platform/windows/test_filesystem_notifications.py (+74/-2)
ubuntuone/platform/windows/filesystem_notifications.py (+16/-4)
To merge this branch: bzr merge lp:~mandel/ubuntuone-client/improve-fs-notifications-logging
Reviewer Review Type Date Requested Status
Natalia Bidart (community) Approve
Diego Sarmentero (community) Approve
Review via email: mp+79243@code.launchpad.net

Commit message

Fixes: lp:872254

* Improves the logging used in the fs events on Windows.
* Adds a logging assertion in the tests to ensure that things are done correctly.

Description of the change

Fixes: lp:872254

* Improves the logging used in the fs events on Windows.
* Adds a logging assertion in the tests to ensure that things are done correctly.

To post a comment you must log in.
Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

+1

review: Approve
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

* The following code is not needed since the memento handler accepts multiple strings in one single call:

119 + def _assert_logs(self, events):
120 + """Assert the debug logs."""
121 + logs = self._get_fs_logs(events)
122 + for msg in logs:
123 + self.assertTrue(self.memento.check_debug(msg))

you just have to do:

        self.assertTrue(self.memento.check_debug(*self._get_fs_logs(events)))

* I also think that all the new helper functions could be improve using list comprehensions, for example:

86 + def _get_event_msgs(self, events):
87 + """Return the pushing event logs."""
88 + logs = []
89 + for e in events:
90 + logs.append('Pushing event %s to processor.' % e)
91 + return logs

can be better read as:

86 + def _get_event_msgs(self, events):
87 + """Return the pushing event logs."""
88 + msg = 'Pushing event %s to processor.'
89 + return [msg % e for e in events]

Anyways, if we use the former to create generators instead of lists in memory, we can improve all the helpers having only one like this:

def _assert_logs(self, events):
    logs = []

    msg = 'Pushing event %r to processor.'
    logs.extend(msg % e for e in events)

    msg = 'Got from ReadDirectoryChangesW %r.'
    logs.extend(msg % [(WINDOWS_ACTIONS_NAMES[a], p) for a, p in actions]
            for actions in self.raw_events)

    msg = 'Is path %r a dir? %r'
    logs.extend(is_dir % data for data in self.paths_checked)

    self.assertTrue(self.memento.check_debug(*logs))

* Also, always user %r to log variables, not %s, since the latter can fail for non-ascii strings.

* For every addHanlder, you need to queue a removeHandler cleanup function.

* The rest looks good!

review: Needs Fixing
1152. By Manuel de la Peña

Done hte follwing:
* use %r for logging.
* simplify the use of helper functions to list comprehentions.

Revision history for this message
Manuel de la Peña (mandel) wrote :
Download full text (3.8 KiB)

> * The following code is not needed since the memento handler accepts multiple
> strings in one single call:
>
> 119 + def _assert_logs(self, events):
> 120 + """Assert the debug logs."""
> 121 + logs = self._get_fs_logs(events)
> 122 + for msg in logs:
> 123 + self.assertTrue(self.memento.check_debug(msg))
>
> you just have to do:
>
> self.assertTrue(self.memento.check_debug(*self._get_fs_logs(events)))
>
> * I also think that all the new helper functions could be improve using list
> comprehensions, for example:
>
> 86 + def _get_event_msgs(self, events):
> 87 + """Return the pushing event logs."""
> 88 + logs = []
> 89 + for e in events:
> 90 + logs.append('Pushing event %s to processor.' % e)
> 91 + return logs
>
> can be better read as:
>
> 86 + def _get_event_msgs(self, events):
> 87 + """Return the pushing event logs."""
> 88 + msg = 'Pushing event %s to processor.'
> 89 + return [msg % e for e in events]
>
> Anyways, if we use the former to create generators instead of lists in memory,
> we can improve all the helpers having only one like this:
>
> def _assert_logs(self, events):
> logs = []
>
> msg = 'Pushing event %r to processor.'
> logs.extend(msg % e for e in events)
>
> msg = 'Got from ReadDirectoryChangesW %r.'
> logs.extend(msg % [(WINDOWS_ACTIONS_NAMES[a], p) for a, p in actions]
> for actions in self.raw_events)
>
> msg = 'Is path %r a dir? %r'
> logs.extend(is_dir % data for data in self.paths_checked)
>
> self.assertTrue(self.memento.check_debug(*logs))
>
> * Also, always user %r to log variables, not %s, since the latter can fail for
> non-ascii strings.
>
> * For every addHanlder, you need to queue a removeHandler cleanup function.
>
> * The rest looks good!

All comments make sense and therefore I have removed all the helper functions, the only issue I found with the comments it he one regarding 'self.assertTrue(self.memento.check_debug(*logs))'. From a conversation in #chicharra with facu:

facundobatista> mandel, pong
 <mandel> facundobatista, can you give me a hand with the memento handler?
 <mandel> facundobatista, there is something I quite not get
 <facundobatista> mandel, yes
 <mandel> facundobatista, what does check_debug do when I pass a collection of expected logs messages?
 <facundobatista> mandel, it checks that *all* those messages are included in a single log line with debug level
 <mandel> facundobatista, ok, is there a method to check that all those logs messages have been added, but not in a single line?
 <mandel> or should I loop though them and do a check_dbug per message?
 <facundobatista> mandel, there's no method... the idea behind those multiple messages, is that you care to "some stuff being logged" (like the word "Starting", and the share id, etc), but you don't care the exact log wording
 <facundobatista> mandel, give me one example of what you're wanting
 <mandel> facundobatista, let me past the example
 <mandel> facundobatista, here: http://paste.ubuntu.com/707372/
 <mandel> facundobatista, I want to ensure that what I passed is indeed present ...

Read more...

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

This code will not be executed if something fails before it runs:

        # clean logger
        manager._wdm[0].log.removeHandler(self.memento)

should be:

        # clean logger
        self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)

(you need to move it up right after the log adding)

* While you're at it, can you please change this call:

        # clean behind us by removing the file
        os.remove(file_name)

to be

        # clean behind us by removing the file
        self.addCleanup(os.remove, file_name)

(you need to move it up right after the file creation)

* The proper styling for this:

# a map of the actions to names so that we have better logs.
WINDOWS_ACTIONS_NAMES = {
  1: 'IN_CREATE',
  2: 'IN_DELETE',
  3: 'IN_MODIFY',
  4: 'IN_MOVED_FROM',
  5: 'IN_MOVED_TO'}

should be:

# a map of the actions to names so that we have better logs.
WINDOWS_ACTIONS_NAMES = {
    1: 'IN_CREATE',
    2: 'IN_DELETE',
    3: 'IN_MODIFY',
    4: 'IN_MOVED_FROM',
    5: 'IN_MOVED_TO',
}

You have my +1 with those changes!

review: Needs Fixing
1153. By Manuel de la Peña

Made changes according to code review.

1154. By Manuel de la Peña

Move the cleanup as close as possible to the file creation.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

The adding of:

        # clean logger
        self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)

needs to be done right after the addHandler, since if the actions() calls fails, the logger is not removed.

The rest looks good!

review: Approve
Revision history for this message
Manuel de la Peña (mandel) wrote :

> The adding of:
>
> # clean logger
> self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
>
> needs to be done right after the addHandler, since if the actions() calls
> fails, the logger is not removed.
>

True, and sorted out!!

> The rest looks good!

1155. By Manuel de la Peña

Add the clean up just after the things that we need to clean.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/platform/windows/test_filesystem_notifications.py'
2--- tests/platform/windows/test_filesystem_notifications.py 2011-10-07 13:53:28 +0000
3+++ tests/platform/windows/test_filesystem_notifications.py 2011-10-18 13:36:35 +0000
4@@ -17,6 +17,7 @@
5 # with this program. If not, see <http://www.gnu.org/licenses/>.
6 """Test the filesystem notifications on windows."""
7
8+import logging
9 import os
10 import tempfile
11 import thread
12@@ -24,8 +25,10 @@
13 import itertools
14
15 from twisted.internet import defer
16+from win32file import FILE_NOTIFY_INFORMATION
17
18 from contrib.testing.testcase import BaseTwistedTestCase
19+from ubuntuone.devtools.handlers import MementoHandler
20 from ubuntuone.platform.windows import os_helper
21 from ubuntuone.platform.windows.pyinotify import (
22 ProcessEvent,
23@@ -47,6 +50,7 @@
24 FILE_NOTIFY_CHANGE_LAST_WRITE,
25 FILE_NOTIFY_CHANGE_SECURITY,
26 FILE_NOTIFY_CHANGE_LAST_ACCESS,
27+ WINDOWS_ACTIONS_NAMES,
28 )
29
30 #create a rever mapping to use it in the tests.
31@@ -114,6 +118,34 @@
32 FILE_NOTIFY_CHANGE_LAST_WRITE | \
33 FILE_NOTIFY_CHANGE_SECURITY | \
34 FILE_NOTIFY_CHANGE_LAST_ACCESS
35+ self.memento = MementoHandler()
36+ self.memento.setLevel(logging.DEBUG)
37+ self.raw_events = []
38+ self.paths_checked = []
39+ old_is_dir = Watch._path_is_dir
40+
41+ def file_notify_information_wrapper(buf, data):
42+ """Wrapper that gets the events and adds them to the list."""
43+ events = FILE_NOTIFY_INFORMATION(buf, data)
44+ # we want to append the list because that is what will be logged.
45+ # If we use extend we wont have the same logging because it will
46+ # group all events in a single lists which is not what the COM API
47+ # does.
48+ str_events = [(WINDOWS_ACTIONS_NAMES[action], path) for action, path in
49+ events]
50+ self.raw_events.append(str_events)
51+ return events
52+
53+ def path_is_dir_wrapper(watch, path):
54+ """Wrapper that gets the checked paths."""
55+ result = old_is_dir(watch, path)
56+ self.paths_checked.append((path, result))
57+ return result
58+
59+ self.patch(filesystem_notifications, 'FILE_NOTIFY_INFORMATION',
60+ file_notify_information_wrapper)
61+ self.patch(filesystem_notifications.Watch, '_path_is_dir',
62+ path_is_dir_wrapper)
63
64 @defer.inlineCallbacks
65 def _perform_operations(self, path, mask, auto_add, actions,
66@@ -123,6 +155,10 @@
67 manager = WatchManager(handler)
68 yield manager.add_watch(os_helper.get_windows_valid_path(path), mask,
69 auto_add=auto_add)
70+ # change the logger so that we can check the logs if we wanted
71+ manager._wdm[0].log.addHandler(self.memento)
72+ # clean logger later
73+ self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
74 # execution the actions
75 actions()
76 # process the recorded events
77@@ -136,6 +172,10 @@
78 manager = WatchManager()
79 manager.add_watch(os_helper.get_windows_valid_path(path), mask,
80 auto_add=auto_add)
81+ # change the logger so that we can check the logs if we wanted
82+ manager._wdm[0].log.addHandler(self.memento)
83+ # clean logger later
84+ self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
85 # execution the actions
86 actions()
87 # process the recorded events
88@@ -143,6 +183,18 @@
89 events = self.handler.processed_events
90 return events
91
92+ def _assert_logs(self, events):
93+ """Assert the debug logs."""
94+ logs = []
95+ msg = 'Is path %r a dir? %s'
96+ logs.extend([msg % data for data in self.paths_checked])
97+ msg = 'Got from ReadDirectoryChangesW %r.'
98+ logs.extend([msg % actions for actions in self.raw_events])
99+ msg = 'Pushing event %r to processor.'
100+ logs.extend([msg % e for e in events])
101+ for msg in logs:
102+ self.assertTrue(self.memento.check_debug(msg))
103+
104 @defer.inlineCallbacks
105 def test_file_create(self):
106 """Test that the correct event is returned on a file create."""
107@@ -166,6 +218,8 @@
108 self.assertEqual('.', event.path)
109 self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
110 self.assertEqual(0, event.wd)
111+ # assert the logging
112+ self._assert_logs(events)
113
114 @defer.inlineCallbacks
115 def test_dir_create(self):
116@@ -186,6 +240,8 @@
117 self.assertEqual('.', event.path)
118 self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
119 self.assertEqual(0, event.wd)
120+ # assert the logging
121+ self._assert_logs(events)
122
123 @defer.inlineCallbacks
124 def test_file_remove(self):
125@@ -208,6 +264,8 @@
126 self.assertEqual('.', event.path)
127 self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
128 self.assertEqual(0, event.wd)
129+ # assert the logging
130+ self._assert_logs(events)
131
132 @defer.inlineCallbacks
133 def test_dir_remove(self):
134@@ -229,6 +287,8 @@
135 self.assertEqual('.', event.path)
136 self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
137 self.assertEqual(0, event.wd)
138+ # assert the logging
139+ self._assert_logs(events)
140
141 @defer.inlineCallbacks
142 def test_file_write(self):
143@@ -236,6 +296,8 @@
144 file_name = os.path.join(self.basedir, 'test_file_write')
145 # create the file before recording
146 fd = open(file_name, 'w')
147+ # clean behind us by removing the file
148+ self.addCleanup(os.remove, file_name)
149
150 def write_file():
151 """Action for the test."""
152@@ -252,8 +314,8 @@
153 self.assertEqual('.', event.path)
154 self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
155 self.assertEqual(0, event.wd)
156- # clean behind us by removeing the file
157- os.remove(file_name)
158+ # assert the logging
159+ self._assert_logs(events)
160
161 @defer.inlineCallbacks
162 def test_file_moved_to_watched_dir_same_watcher(self):
163@@ -296,6 +358,8 @@
164 self.assertEqual(0, move_to_event.wd)
165 # assert that both cookies are the same
166 self.assertEqual(move_from_event.cookie, move_to_event.cookie)
167+ # assert the logging
168+ self._assert_logs(events)
169
170 @defer.inlineCallbacks
171 def test_file_moved_to_not_watched_dir(self):
172@@ -323,6 +387,8 @@
173 self.assertEqual(os.path.join(self.basedir, from_file_name),
174 event.pathname)
175 self.assertEqual(0, event.wd)
176+ # assert the logging
177+ self._assert_logs(events)
178
179 @defer.inlineCallbacks
180 def test_file_move_from_not_watched_dir(self):
181@@ -351,6 +417,8 @@
182 self.assertEqual(os.path.join(self.basedir, to_file_name),
183 event.pathname)
184 self.assertEqual(0, event.wd)
185+ # assert the logging
186+ self._assert_logs(events)
187
188 @defer.inlineCallbacks
189 def test_dir_moved_to_watched_dir_same_watcher(self):
190@@ -391,6 +459,8 @@
191 self.assertEqual(0, move_to_event.wd)
192 # assert that both cookies are the same
193 self.assertEqual(move_from_event.cookie, move_to_event.cookie)
194+ # assert the logging
195+ self._assert_logs(events)
196
197 @defer.inlineCallbacks
198 def test_dir_moved_to_not_watched_dir(self):
199@@ -414,6 +484,8 @@
200 self.assertEqual('.', event.path)
201 self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
202 self.assertEqual(0, event.wd)
203+ # assert the logging
204+ self._assert_logs(events)
205
206 @defer.inlineCallbacks
207 def test_dir_move_from_not_watched_dir(self):
208
209=== modified file 'ubuntuone/platform/windows/filesystem_notifications.py'
210--- ubuntuone/platform/windows/filesystem_notifications.py 2011-10-14 20:02:23 +0000
211+++ ubuntuone/platform/windows/filesystem_notifications.py 2011-10-18 13:36:35 +0000
212@@ -90,7 +90,17 @@
213 2: IN_DELETE,
214 3: IN_MODIFY,
215 4: IN_MOVED_FROM,
216- 5: IN_MOVED_TO}
217+ 5: IN_MOVED_TO,
218+}
219+
220+# a map of the actions to names so that we have better logs.
221+WINDOWS_ACTIONS_NAMES = {
222+ 1: 'IN_CREATE',
223+ 2: 'IN_DELETE',
224+ 3: 'IN_MODIFY',
225+ 4: 'IN_MOVED_FROM',
226+ 5: 'IN_MOVED_TO',
227+}
228
229 # translates quickly the event and it's is_dir state to our standard events
230 NAME_TRANSLATIONS = {
231@@ -188,7 +198,7 @@
232 else:
233 # path does not exists, was it in the internal list?
234 is_dir = path in self._subdirs
235- self.log.debug('is path %r a dir? %s', path, is_dir)
236+ self.log.debug('Is path %r a dir? %s', path, is_dir)
237 return is_dir
238
239 def _process_events(self, events):
240@@ -239,7 +249,7 @@
241 # add the event only if we do not have an exclude filter or
242 # the exclude filter returns False, that is, the event will not
243 # be excluded
244- self.log.debug('Event is %s.', event)
245+ self.log.debug('Pushing event %r to processor.', event)
246 self._processor(event)
247
248 def _call_deferred(self, f, *args):
249@@ -310,7 +320,9 @@
250 data = GetOverlappedResult(handle, self._overlapped, True)
251 # lets ead the data and store it in the results
252 events = FILE_NOTIFY_INFORMATION(buf, data)
253- self.log.debug('Events from ReadDirectoryChangesW are %s', events)
254+ self.log.debug('Got from ReadDirectoryChangesW %r.',
255+ [(WINDOWS_ACTIONS_NAMES[action], path) for action, path in
256+ events])
257 reactor.callFromThread(self._process_events, events)
258
259 @is_valid_windows_path(path_indexes=[1])

Subscribers

People subscribed via source and target branches