The test suite revealed a bug about using a bad type for a method argument:
01:48:12.167 [INFO] [exec] FAIL nuxeo-drive-client/tests/test_watchers.py::TestWatchers::test_local_scan_encoding 01:48:12.167 [INFO] [exec] =================================== FAILURES =================================== 01:48:12.167 [INFO] [exec] ____________________ TestWatchers.test_local_scan_encoding _____________________ 01:48:12.167 [INFO] [exec] self = <tests.test_watchers.TestWatchers testMethod=test_local_scan_encoding> 01:48:12.167 [INFO] [exec] def test_local_scan_encoding(self): 01:48:12.167 [INFO] [exec] local = self.local_client_1 01:48:12.167 [INFO] [exec] remote = self.remote_document_client_1 01:48:12.167 [INFO] [exec] # Synchronize test workspace 01:48:12.167 [INFO] [exec] self.engine_1.start() 01:48:12.167 [INFO] [exec] self.wait_sync() 01:48:12.167 [INFO] [exec] self.engine_1.stop() 01:48:12.167 [INFO] [exec] # Create files with Unicode combining accents, Unicode latin characters and no special characters 01:48:12.167 [INFO] [exec] local.make_file(u'/', u'Accentue\u0301.odt', u'Content') 01:48:12.167 [INFO] [exec] local.make_folder(u'/', u'P\xf4le applicatif') 01:48:12.167 [INFO] [exec] local.make_file(u'/P\xf4le applicatif', u'e\u0302tre ou ne pas \xeatre.odt', u'Content') 01:48:12.167 [INFO] [exec] local.make_file(u'/', u'No special character.odt', u'Content') 01:48:12.167 [INFO] [exec] # Launch local scan and check upstream synchronization 01:48:12.167 [INFO] [exec] self.engine_1.start() 01:48:12.167 [INFO] [exec] self.wait_sync() 01:48:12.167 [INFO] [exec] self.engine_1.stop() 01:48:12.167 [INFO] [exec] self.assertTrue(remote.exists(u'/Accentue\u0301.odt')) 01:48:12.167 [INFO] [exec] self.assertTrue(remote.exists(u'/P\xf4le applicatif')) 01:48:12.167 [INFO] [exec] self.assertTrue(remote.exists(u'/P\xf4le applicatif/e\u0302tre ou ne pas \xeatre.odt')) 01:48:12.167 [INFO] [exec] self.assertTrue(remote.exists(u'/No special character.odt')) 01:48:12.167 [INFO] [exec] # Check rename using normalized names as previous local scan has normalized them on the file system 01:48:12.167 [INFO] [exec] local.rename(u'/Accentu\xe9.odt', u'Accentue\u0301 avec un e\u0302 et un \xe9.odt') 01:48:12.167 [INFO] [exec] local.rename(u'/P\xf4le applicatif', u'P\xf4le applique\u0301') 01:48:12.167 [INFO] [exec] # LocalClient.rename calls LocalClient.get_info then the FileInfo constructor which normalizes names 01:48:12.167 [INFO] [exec] # on the file system, thus we need to use the normalized name for the parent folder 01:48:12.167 [INFO] [exec] local.rename(u'/P\xf4le appliqu\xe9/\xeatre ou ne pas \xeatre.odt', u'avoir et e\u0302tre.odt') 01:48:12.167 [INFO] [exec] self.engine_1.start() 01:48:12.167 [INFO] [exec] self.wait_sync(wait_for_async=True) 01:48:12.167 [INFO] [exec] self.engine_1.stop() 01:48:12.167 [INFO] [exec] self.assertEqual(remote.get_info(u'/Accentue\u0301.odt').name, u'Accentu\xe9 avec un \xea et un \xe9.odt') 01:48:12.167 [INFO] [exec] self.assertEqual(remote.get_info(u'/P\xf4le applicatif').name, u'P\xf4le appliqu\xe9') 01:48:12.167 [INFO] [exec] self.assertEqual(remote.get_info(u'/P\xf4le applicatif/e\u0302tre ou ne pas \xeatre.odt').name, 01:48:12.167 [INFO] [exec] u'avoir et \xeatre.odt') 01:48:12.167 [INFO] [exec] # Check content update 01:48:12.167 [INFO] [exec] # NXDRIVE-389: Reload the engine to be sure that the pair are all synchronized 01:48:12.167 [INFO] [exec] log.debug("Update content of avoir et etre") 01:48:12.167 [INFO] [exec] local.update_content(u'/Accentu\xe9 avec un \xea et un \xe9.odt', u'Updated content') 01:48:12.167 [INFO] [exec] > local.update_content(u'/P\xf4le appliqu\xe9/avoir et \xeatre.odt', u'Updated content') 01:48:12.167 [INFO] [exec] local = <LocalClient base_folder=u'/opt/jenkins/workspace/Drive_FT-drive_master-CUACR5....dwl2', '.part', '.lock', '.crdownload', '.tmp', '.LOCK', '.bak', '.dwl', '~')> 01:48:12.167 [INFO] [exec] remote = <RemoteDocumentClientForTests base_folder=None, blob_timeout=60, check_suspend...UL3SF5V2PSVOUZC4CRBQLRIA/tmp/tmpBL2OTE-nxdrive-uploads', user_id='driveuser_1'> 01:48:12.167 [INFO] [exec] self = <tests.test_watchers.TestWatchers testMethod=test_local_scan_encoding> 01:48:12.167 [INFO] [exec] nuxeo-drive-client/tests/test_watchers.py:217: 01:48:12.167 [INFO] [exec] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 01:48:12.167 [INFO] [exec] self = <LocalClient base_folder=u'/opt/jenkins/workspace/Drive_FT-drive_master-CUACR5....dwl2', '.part', '.lock', '.crdownload', '.tmp', '.LOCK', '.bak', '.dwl', '~')> 01:48:12.167 [INFO] [exec] ref = '/Pôle appliqué/avoir et être.odt', content = 'Updated content' 01:48:12.167 [INFO] [exec] xattr_names = ('n', 'd', 'r', 'i', 'v', 'e') 01:48:12.167 [INFO] [exec] def update_content(self, ref, content, xattr_names=tuple('ndrive')): 01:48:12.167 [INFO] [exec] xattrs = {} 01:48:12.167 [INFO] [exec] for name in xattr_names: 01:48:12.167 [INFO] [exec] xattrs[name] = self.get_remote_id(ref, name=name) 01:48:12.167 [INFO] [exec] > with open(self.abspath(ref), "wb") as f: 01:48:12.167 [INFO] [exec] E IOError: [Errno 2] No such file or directory: u'/opt/jenkins/workspace/Drive_FT-drive_master-CUACR5UOIAO62W7K2CAAQ7UISS5UUL3SF5V2PSVOUZC4CRBQLRIA/tmp/tmpGp9Ecpdrive-1/Nuxeo Drive/Nuxeo Drive Test Workspace/P\xf4le appliqu\xe9/avoir et \xeatre.odt' 01:48:12.167 [INFO] [exec] content = 'Updated content' 01:48:12.167 [INFO] [exec] name = 'e' 01:48:12.167 [INFO] [exec] ref = '/P\xf4le appliqu\xe9/avoir et \xeatre.odt' 01:48:12.167 [INFO] [exec] self = <LocalClient base_folder=u'/opt/jenkins/workspace/Drive_FT-drive_master-CUACR5....dwl2', '.part', '.lock', '.crdownload', '.tmp', '.LOCK', '.bak', '.dwl', '~')> 01:48:12.167 [INFO] [exec] xattr_names = ('n', 'd', 'r', 'i', 'v', 'e') 01:48:12.167 [INFO] [exec] xattrs = {'d': None, 'e': None, 'i': None, 'n': None, ...} 01:48:12.167 [INFO] [exec] nuxeo-drive-client/nxdrive/client/local_client.py:651: IOError
xattr_names should be equal to ('ndrive',), not ('n', 'd', 'r', 'i', 'v', 'e').