~ubuntu-branches/ubuntu/precise/ubuntuone-client/precise

« back to all changes in this revision

Viewing changes to tests/platform/windows/test_filesystem_notifications.py

  • Committer: Package Import Robot
  • Author(s): Rodney Dawes
  • Date: 2011-12-21 15:46:25 UTC
  • mfrom: (1.1.56)
  • Revision ID: package-import@ubuntu.com-20111221154625-ujvunri4frsecj2k
Tags: 2.99.0-0ubuntu1
* New upstream release.
  - Verify timestamp to avoid invalid auth failures (LP: #692597)
  - Files in new UDFs not uploaded due to filtering (LP: #869920)
* debian/patches:
  - Remove upstreamed patches

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# with this program.  If not, see <http://www.gnu.org/licenses/>.
18
18
"""Test the filesystem notifications on windows."""
19
19
 
 
20
import logging
20
21
import os
21
22
import tempfile
22
23
import thread
24
25
import itertools
25
26
 
26
27
from twisted.internet import defer
 
28
from win32file import FILE_NOTIFY_INFORMATION
27
29
 
28
30
from contrib.testing.testcase import BaseTwistedTestCase
 
31
from ubuntuone.devtools.handlers import MementoHandler
29
32
from ubuntuone.platform.windows import os_helper
30
33
from ubuntuone.platform.windows.pyinotify import (
31
34
    ProcessEvent,
 
35
    IN_CLOSE_WRITE,
 
36
    IN_CREATE,
 
37
    IN_DELETE,
32
38
    IN_OPEN,
33
 
    IN_CLOSE_WRITE
34
39
)
35
40
from ubuntuone.platform.windows import filesystem_notifications
36
41
from ubuntuone.platform.windows.filesystem_notifications import (
45
50
    FILE_NOTIFY_CHANGE_LAST_WRITE,
46
51
    FILE_NOTIFY_CHANGE_SECURITY,
47
52
    FILE_NOTIFY_CHANGE_LAST_ACCESS,
 
53
    WINDOWS_ACTIONS_NAMES,
48
54
)
49
55
 
 
56
#create a rever mapping to use it in the tests.
 
57
REVERSE_WINDOWS_ACTIONS = {}
 
58
for key, value in filesystem_notifications.WINDOWS_ACTIONS.iteritems():
 
59
    REVERSE_WINDOWS_ACTIONS[value] = key
 
60
 
50
61
 
51
62
class FakeException(Exception):
52
63
    """A fake Exception used in tests."""
96
107
 
97
108
    timeout = 5
98
109
 
 
110
    @defer.inlineCallbacks
99
111
    def setUp(self):
100
 
        """Set infor for the tests."""
101
 
        super(TestWatch, self).setUp()
 
112
        yield super(TestWatch, self).setUp()
102
113
        self.basedir = self.mktemp('test_root')
103
114
        self.mask = FILE_NOTIFY_CHANGE_FILE_NAME | \
104
115
            FILE_NOTIFY_CHANGE_DIR_NAME | \
107
118
            FILE_NOTIFY_CHANGE_LAST_WRITE | \
108
119
            FILE_NOTIFY_CHANGE_SECURITY | \
109
120
            FILE_NOTIFY_CHANGE_LAST_ACCESS
110
 
 
 
121
        self.memento = MementoHandler()
 
122
        self.memento.setLevel(logging.DEBUG)
 
123
        self.raw_events = []
 
124
        self.paths_checked = []
 
125
        old_is_dir = Watch._path_is_dir
 
126
 
 
127
        def file_notify_information_wrapper(buf, data):
 
128
            """Wrapper that gets the events and adds them to the list."""
 
129
            events = FILE_NOTIFY_INFORMATION(buf, data)
 
130
            # we want to append the list because that is what will be logged.
 
131
            # If we use extend we wont have the same logging because it will
 
132
            # group all events in a single lists which is not what the COM API
 
133
            # does.
 
134
            str_events = [(WINDOWS_ACTIONS_NAMES[action], path) for action, path in
 
135
                          events]
 
136
            self.raw_events.append(str_events)
 
137
            return events
 
138
 
 
139
        def path_is_dir_wrapper(watch, path):
 
140
            """Wrapper that gets the checked paths."""
 
141
            result = old_is_dir(watch, path)
 
142
            self.paths_checked.append((path, result))
 
143
            return result
 
144
 
 
145
        self.patch(filesystem_notifications, 'FILE_NOTIFY_INFORMATION',
 
146
                   file_notify_information_wrapper)
 
147
        self.patch(filesystem_notifications.Watch, '_path_is_dir',
 
148
                   path_is_dir_wrapper)
111
149
 
112
150
    @defer.inlineCallbacks
113
 
    def _perform_operations(self, path, mask, auto_add, actions, number_events):
 
151
    def _perform_operations(self, path, mask, auto_add, actions,
 
152
                            number_events):
114
153
        """Perform the file operations and returns the recorded events."""
115
154
        handler = TestCaseHandler(number_events=number_events)
116
155
        manager = WatchManager(handler)
117
156
        yield manager.add_watch(os_helper.get_windows_valid_path(path), mask,
118
157
                          auto_add=auto_add)
 
158
        # change the logger so that we can check the logs if we wanted
 
159
        manager._wdm[0].log.addHandler(self.memento)
 
160
        # clean logger later
 
161
        self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
119
162
        # execution the actions
120
163
        actions()
121
164
        # process the recorded events
129
172
        manager = WatchManager()
130
173
        manager.add_watch(os_helper.get_windows_valid_path(path), mask,
131
174
                          auto_add=auto_add)
 
175
        # change the logger so that we can check the logs if we wanted
 
176
        manager._wdm[0].log.addHandler(self.memento)
 
177
        # clean logger later
 
178
        self.addCleanup(manager._wdm[0].log.removeHandler, self.memento)
132
179
        # execution the actions
133
180
        actions()
134
181
        # process the recorded events
136
183
        events = self.handler.processed_events
137
184
        return events
138
185
 
 
186
    def _assert_logs(self, events):
 
187
        """Assert the debug logs."""
 
188
        logs = []
 
189
        msg = 'Is path %r a dir? %s'
 
190
        logs.extend([msg % data for data in self.paths_checked])
 
191
        msg = 'Got from ReadDirectoryChangesW %r.'
 
192
        logs.extend([msg % actions for actions in self.raw_events])
 
193
        msg = 'Pushing event %r to processor.'
 
194
        logs.extend([msg % e for e in events])
 
195
        for msg in logs:
 
196
            self.assertTrue(self.memento.check_debug(msg))
 
197
 
139
198
    @defer.inlineCallbacks
140
199
    def test_file_create(self):
141
200
        """Test that the correct event is returned on a file create."""
159
218
        self.assertEqual('.', event.path)
160
219
        self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
161
220
        self.assertEqual(0, event.wd)
 
221
        # assert the logging
 
222
        self._assert_logs(events)
162
223
 
163
224
    @defer.inlineCallbacks
164
225
    def test_dir_create(self):
179
240
        self.assertEqual('.', event.path)
180
241
        self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
181
242
        self.assertEqual(0, event.wd)
 
243
        # assert the logging
 
244
        self._assert_logs(events)
182
245
 
183
246
    @defer.inlineCallbacks
184
247
    def test_file_remove(self):
201
264
        self.assertEqual('.', event.path)
202
265
        self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
203
266
        self.assertEqual(0, event.wd)
 
267
        # assert the logging
 
268
        self._assert_logs(events)
204
269
 
205
270
    @defer.inlineCallbacks
206
271
    def test_dir_remove(self):
222
287
        self.assertEqual('.', event.path)
223
288
        self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
224
289
        self.assertEqual(0, event.wd)
 
290
        # assert the logging
 
291
        self._assert_logs(events)
225
292
 
226
293
    @defer.inlineCallbacks
227
294
    def test_file_write(self):
229
296
        file_name = os.path.join(self.basedir, 'test_file_write')
230
297
        # create the file before recording
231
298
        fd = open(file_name, 'w')
 
299
        # clean behind us by removing the file
 
300
        self.addCleanup(os.remove, file_name)
 
301
 
232
302
        def write_file():
233
303
            """Action for the test."""
234
304
            fd.write('test')
244
314
        self.assertEqual('.', event.path)
245
315
        self.assertEqual(os.path.join(self.basedir, file_name), event.pathname)
246
316
        self.assertEqual(0, event.wd)
247
 
        # clean behind us by removeing the file
248
 
        os.remove(file_name)
 
317
        # assert the logging
 
318
        self._assert_logs(events)
249
319
 
250
320
    @defer.inlineCallbacks
251
321
    def test_file_moved_to_watched_dir_same_watcher(self):
269
339
        self.assertFalse(move_from_event.dir)
270
340
        self.assertEqual(0x40, move_from_event.mask)
271
341
        self.assertEqual('IN_MOVED_FROM', move_from_event.maskname)
272
 
        self.assertEqual(os.path.split(from_file_name)[1], move_from_event.name)
 
342
        self.assertEqual(os.path.split(from_file_name)[1],
 
343
                         move_from_event.name)
273
344
        self.assertEqual('.', move_from_event.path)
274
345
        self.assertEqual(os.path.join(self.basedir, from_file_name),
275
346
            move_from_event.pathname)
287
358
        self.assertEqual(0, move_to_event.wd)
288
359
        # assert that both cookies are the same
289
360
        self.assertEqual(move_from_event.cookie, move_to_event.cookie)
 
361
        # assert the logging
 
362
        self._assert_logs(events)
290
363
 
291
364
    @defer.inlineCallbacks
292
365
    def test_file_moved_to_not_watched_dir(self):
311
384
        self.assertEqual('IN_DELETE', event.maskname)
312
385
        self.assertEqual(os.path.split(from_file_name)[1], event.name)
313
386
        self.assertEqual('.', event.path)
314
 
        self.assertEqual(os.path.join(self.basedir, from_file_name), event.pathname)
 
387
        self.assertEqual(os.path.join(self.basedir, from_file_name),
 
388
                         event.pathname)
315
389
        self.assertEqual(0, event.wd)
 
390
        # assert the logging
 
391
        self._assert_logs(events)
316
392
 
317
393
    @defer.inlineCallbacks
318
394
    def test_file_move_from_not_watched_dir(self):
341
417
        self.assertEqual(os.path.join(self.basedir, to_file_name),
342
418
            event.pathname)
343
419
        self.assertEqual(0, event.wd)
 
420
        # assert the logging
 
421
        self._assert_logs(events)
344
422
 
345
423
    @defer.inlineCallbacks
346
424
    def test_dir_moved_to_watched_dir_same_watcher(self):
376
454
        self.assertEqual('.', move_to_event.path)
377
455
        self.assertEqual(os.path.join(self.basedir, to_dir_name),
378
456
            move_to_event.pathname)
379
 
        self.assertEqual(os.path.split(from_dir_name)[1], move_to_event.src_pathname)
 
457
        self.assertEqual(os.path.split(from_dir_name)[1],
 
458
                         move_to_event.src_pathname)
380
459
        self.assertEqual(0, move_to_event.wd)
381
460
        # assert that both cookies are the same
382
461
        self.assertEqual(move_from_event.cookie, move_to_event.cookie)
 
462
        # assert the logging
 
463
        self._assert_logs(events)
383
464
 
384
465
    @defer.inlineCallbacks
385
466
    def test_dir_moved_to_not_watched_dir(self):
403
484
        self.assertEqual('.', event.path)
404
485
        self.assertEqual(os.path.join(self.basedir, dir_name), event.pathname)
405
486
        self.assertEqual(0, event.wd)
 
487
        # assert the logging
 
488
        self._assert_logs(events)
406
489
 
407
490
    @defer.inlineCallbacks
408
491
    def test_dir_move_from_not_watched_dir(self):
426
509
        self.assertEqual('IN_CREATE|IN_ISDIR', event.maskname)
427
510
        self.assertEqual(os.path.split(from_dir_name)[1], event.name)
428
511
        self.assertEqual('.', event.path)
429
 
        self.assertEqual(os.path.join(self.basedir, to_dir_name), event.pathname)
 
512
        self.assertEqual(os.path.join(self.basedir, to_dir_name),
 
513
                         event.pathname)
430
514
        self.assertEqual(0, event.wd)
431
515
 
432
516
    def test_exclude_filter(self):
436
520
        # add a watch that will always exclude all actions
437
521
        manager.add_watch(os_helper.get_windows_valid_path(self.basedir),
438
522
                          self.mask, auto_add=True,
439
 
                          exclude_filter=lambda x: True )
 
523
                          exclude_filter=lambda x: True)
440
524
        # execution the actions
441
525
        file_name = os.path.join(self.basedir, 'test_file_create')
442
526
        open(file_name, 'w').close()
666
750
    @defer.inlineCallbacks
667
751
    def test_stop_watching_fired_when_watch_thread_finishes(self):
668
752
        """The deferred returned is fired when the watch thread finishes."""
669
 
        events = []
670
753
        test_path = self.mktemp("another_test_directory")
671
 
        fake_processor = events.append
672
 
        watch = Watch(1, test_path, self.mask, True, fake_processor)
 
754
        watch = Watch(1, test_path, self.mask, True, None)
673
755
        yield watch.start_watching()
674
756
        self.assertNotEqual(watch._watch_handle, None)
675
757
        yield watch.stop_watching()
676
758
        self.assertEqual(watch._watch_handle, None)
677
759
 
 
760
    def test_is_path_dir_missing_no_subdir(self):
 
761
        """Test when the path does not exist and is no a subdir."""
 
762
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
763
        test_path = self.mktemp("test_directory")
 
764
        self.patch(os.path, 'exists', lambda path: False)
 
765
        watch = Watch(1, test_path, self.mask, True, None)
 
766
        self.assertFalse(watch._path_is_dir(path))
 
767
 
 
768
    def test_is_path_dir_missing_in_subdir(self):
 
769
        """Test when the path does not exist and is a subdir."""
 
770
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
771
        test_path = self.mktemp("test_directory")
 
772
        self.patch(os.path, 'exists', lambda path: False)
 
773
        watch = Watch(1, test_path, self.mask, True, None)
 
774
        watch._subdirs.add(path)
 
775
        self.assertTrue(watch._path_is_dir(path))
 
776
 
 
777
    def test_is_path_dir_present_is_dir(self):
 
778
        """Test when the path is present and is dir."""
 
779
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
780
        test_path = self.mktemp("test_directory")
 
781
        self.patch(os.path, 'exists', lambda path: True)
 
782
        self.patch(os.path, 'isdir', lambda path: True)
 
783
        watch = Watch(1, test_path, self.mask, True, None)
 
784
        watch._subdirs.add(path)
 
785
        self.assertTrue(watch._path_is_dir(path))
 
786
 
 
787
    def test_is_path_dir_present_no_dir(self):
 
788
        """Test when the path is present but not a dir."""
 
789
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
790
        test_path = self.mktemp("test_directory")
 
791
        self.patch(os.path, 'exists', lambda path: True)
 
792
        self.patch(os.path, 'isdir', lambda path: False)
 
793
        watch = Watch(1, test_path, self.mask, True, None)
 
794
        watch._subdirs.add(path)
 
795
        self.assertFalse(watch._path_is_dir(path))
 
796
 
 
797
    def test_update_subdirs_create_not_present(self):
 
798
        """Test when we update on a create event and not present."""
 
799
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
800
        test_path = self.mktemp("test_directory")
 
801
        watch = Watch(1, test_path, self.mask, True, None)
 
802
        watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_CREATE])
 
803
        self.assertTrue(path in watch._subdirs)
 
804
 
 
805
    def test_update_subdirs_create_present(self):
 
806
        """Test when we update on a create event and is present."""
 
807
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
808
        test_path = self.mktemp("test_directory")
 
809
        watch = Watch(1, test_path, self.mask, True, None)
 
810
        watch._subdirs.add(path)
 
811
        old_length = len(watch._subdirs)
 
812
        watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_CREATE])
 
813
        self.assertTrue(path in watch._subdirs)
 
814
        self.assertEqual(old_length, len(watch._subdirs))
 
815
 
 
816
    def test_update_subdirs_delete_not_present(self):
 
817
        """Test when we delete and is not present."""
 
818
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
819
        test_path = self.mktemp("test_directory")
 
820
        watch = Watch(1, test_path, self.mask, True, None)
 
821
        watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_DELETE])
 
822
        self.assertTrue(path not in watch._subdirs)
 
823
 
 
824
    def test_update_subdirs_delete_present(self):
 
825
        """Test when we delete and is present."""
 
826
        path = u'\\\\?\\C:\\path\\to\\no\\dir'
 
827
        test_path = self.mktemp("test_directory")
 
828
        watch = Watch(1, test_path, self.mask, True, None)
 
829
        watch._subdirs.add(path)
 
830
        watch._update_subdirs(path, REVERSE_WINDOWS_ACTIONS[IN_DELETE])
 
831
        self.assertTrue(path not in watch._subdirs)
 
832
 
678
833
 
679
834
class TestWatchManager(BaseTwistedTestCase):
680
835
    """Test the watch manager."""
681
836
 
 
837
    @defer.inlineCallbacks
682
838
    def setUp(self):
683
839
        """Set each of the tests."""
684
 
        super(TestWatchManager, self).setUp()
 
840
        yield super(TestWatchManager, self).setUp()
685
841
        self.parent_path = u'\\\\?\\C:\\'  # a valid windows path
686
842
        self.path = self.parent_path + u'path'
687
843
        self.watch = Watch(1, self.path, None, True, None)
954
1110
class TestNotifyProcessor(BaseTwistedTestCase):
955
1111
    """Test the notify processor."""
956
1112
 
 
1113
    @defer.inlineCallbacks
957
1114
    def setUp(self):
958
1115
        """set up the diffeent tests."""
959
 
        super(TestNotifyProcessor, self).setUp()
 
1116
        yield super(TestNotifyProcessor, self).setUp()
960
1117
        self.processor = NotifyProcessor(None)
961
1118
        self.general = FakeGeneralProcessor()
962
1119
        self.processor.general_processor = self.general
1067
1224
        self.assertEqual(5, len(self.general.called_methods))
1068
1225
        # assert that the ignores are called
1069
1226
        self.assertEqual('is_ignored', self.general.called_methods[0][0])
1070
 
        self.assertEqual(held_event.pathname, self.general.called_methods[0][1])
 
1227
        self.assertEqual(held_event.pathname,
 
1228
                         self.general.called_methods[0][1])
1071
1229
        self.assertEqual('is_ignored', self.general.called_methods[1][0])
1072
1230
        self.assertEqual(event.pathname, self.general.called_methods[1][1])
1073
1231
        # assert that we do request the share_id
1074
 
        self.assertEqual('get_path_share_id', self.general.called_methods[2][0])
 
1232
        self.assertEqual('get_path_share_id',
 
1233
                         self.general.called_methods[2][0])
1075
1234
        self.assertEqual(os.path.split(event.pathname)[0],
1076
1235
                         self.general.called_methods[2][1],
1077
1236
                         'Get the share_id for event')
1078
 
        self.assertEqual('get_path_share_id', self.general.called_methods[3][0])
 
1237
        self.assertEqual('get_path_share_id',
 
1238
                         self.general.called_methods[3][0])
1079
1239
        self.assertEqual(os.path.split(held_event.pathname)[0],
1080
1240
                         self.general.called_methods[3][1],
1081
1241
                         'Get the share_id for held event.')
1083
1243
        self.assertEqual('eq_push', self.general.called_methods[4][0])
1084
1244
        self.assertEqual('FS_DIR_MOVE', self.general.called_methods[4][1])
1085
1245
        self.assertEqual(event.pathname, self.general.called_methods[4][3])
1086
 
        self.assertEqual(held_event.pathname, self.general.called_methods[4][4])
 
1246
        self.assertEqual(held_event.pathname,
 
1247
                         self.general.called_methods[4][4])
1087
1248
 
1088
1249
    def test_process_IN_MOVED_TO_file(self):
1089
1250
        """Test that the in moved to works as expected."""
1099
1260
        self.assertEqual(5, len(self.general.called_methods))
1100
1261
        # assert that the ignores are called
1101
1262
        self.assertEqual('is_ignored', self.general.called_methods[0][0])
1102
 
        self.assertEqual(held_event.pathname, self.general.called_methods[0][1])
 
1263
        self.assertEqual(held_event.pathname,
 
1264
                         self.general.called_methods[0][1])
1103
1265
        self.assertEqual('is_ignored', self.general.called_methods[1][0])
1104
1266
        self.assertEqual(event.pathname, self.general.called_methods[1][1])
1105
1267
        # assert that we do request the share_id
1106
 
        self.assertEqual('get_path_share_id', self.general.called_methods[2][0])
 
1268
        self.assertEqual('get_path_share_id',
 
1269
                         self.general.called_methods[2][0])
1107
1270
        self.assertEqual(os.path.split(event.pathname)[0],
1108
1271
                         self.general.called_methods[2][1],
1109
1272
                         'Get the share_id for event')
1110
 
        self.assertEqual('get_path_share_id', self.general.called_methods[3][0])
 
1273
        self.assertEqual('get_path_share_id',
 
1274
                         self.general.called_methods[3][0])
1111
1275
        self.assertEqual(os.path.split(held_event.pathname)[0],
1112
1276
                         self.general.called_methods[3][1],
1113
1277
                         'Get the share_id for held event.')
1115
1279
        self.assertEqual('eq_push', self.general.called_methods[4][0])
1116
1280
        self.assertEqual('FS_FILE_MOVE', self.general.called_methods[4][1])
1117
1281
        self.assertEqual(event.pathname, self.general.called_methods[4][3])
1118
 
        self.assertEqual(held_event.pathname, self.general.called_methods[4][4])
 
1282
        self.assertEqual(held_event.pathname,
 
1283
                         self.general.called_methods[4][4])
1119
1284
 
1120
1285
    def test_fake_create_event_dir(self):
1121
1286
        """Test that the in moved to works as expected."""
1152
1317
        self.assertEqual(2, len(self.general.called_methods))
1153
1318
        self.assertEqual('eq_push', self.general.called_methods[0][0])
1154
1319
        self.assertEqual('FS_DIR_DELETE', self.general.called_methods[0][1])
1155
 
        self.assertEqual(held_event.pathname, self.general.called_methods[0][2])
 
1320
        self.assertEqual(held_event.pathname,
 
1321
                         self.general.called_methods[0][2])
1156
1322
        self.assertEqual('eq_push', self.general.called_methods[1][0])
1157
1323
        self.assertEqual('FS_DIR_CREATE', self.general.called_methods[1][1])
1158
1324
        self.assertEqual(event.pathname, self.general.called_methods[1][2])
1168
1334
        self.assertEqual(3, len(self.general.called_methods))
1169
1335
        self.assertEqual('eq_push', self.general.called_methods[0][0])
1170
1336
        self.assertEqual('FS_FILE_DELETE', self.general.called_methods[0][1])
1171
 
        self.assertEqual(held_event.pathname, self.general.called_methods[0][2])
 
1337
        self.assertEqual(held_event.pathname,
 
1338
                         self.general.called_methods[0][2])
1172
1339
        self.assertEqual('eq_push', self.general.called_methods[1][0])
1173
1340
        self.assertEqual('FS_FILE_CREATE', self.general.called_methods[1][1])
1174
1341
        self.assertEqual(event.pathname, self.general.called_methods[1][2])
1327
1494
                """Create a new instance."""
1328
1495
                self.ancestors = ancestors
1329
1496
 
1330
 
        ancestors = ['~', '~\\Pictures', '~\\Pictures\\Home',]
 
1497
        ancestors = ['~', '~\\Pictures', '~\\Pictures\\Home', ]
1331
1498
        volume = FakeVolume(ancestors)
1332
1499
        monitor = FilesystemMonitor(None, None)
1333
1500
        added = yield monitor.add_watches_to_udf_ancestors(volume)