Namespaces are disabled by default (can be enabled at runtime with a flag). OSX compa...
[rrq/fuse_xattrs.git] / test / tests.py
1 #!/usr/bin/env python3
2
3
4 # fuse_xattrs - Add xattrs support using sidecar files
5 #
6 # Copyright (C) 2016  Felipe Barriga Richards <felipe {at} felipebarriga.cl>
7 #
8 # This program can be distributed under the terms of the GNU GPL.
9 # See the file COPYING.
10
11
12 import unittest
13 import xattr
14 from pathlib import Path
15 import os
16 import errno
17 import sys
18
19 if xattr.__version__ != '0.9.1':
20     print("WARNING, only tested with xattr version 0.9.1")
21
22 # Linux / BSD Compatibility
23 ENOATTR = errno.ENODATA
24 if hasattr(errno, "ENOATTR"):
25     ENOATTR = errno.ENOATTR
26
27 # TODO
28 # - listxattr: list too long
29 # - sidecar file permissions
30 # - corrupt metadata files
31
32 skipNamespaceTests = False
33
34
35 class TestXAttrBase(unittest.TestCase):
36     def setUp(self):
37         self.sourceDir = "./source/"
38         self.mountDir = "./mount/"
39         self.randomFilename = "foo.txt"
40
41         self.randomFile = self.mountDir + self.randomFilename
42         self.randomFileSidecar = self.randomFile + ".xattr"
43
44         self.randomSourceFile = self.sourceDir + self.randomFilename
45         self.randomSourceFileSidecar = self.randomSourceFile + ".xattr"
46
47         if os.path.isfile(self.randomFile):
48             os.remove(self.randomFile)
49
50         if os.path.isfile(self.randomFileSidecar):
51             os.remove(self.randomFileSidecar)
52
53         Path(self.randomFile).touch()
54         self.assertTrue(os.path.isfile(self.randomFile))
55         self.assertFalse(os.path.isfile(self.randomFileSidecar))
56
57     def tearDown(self):
58         if os.path.isfile(self.randomFile):
59             os.remove(self.randomFile)
60
61         if os.path.isfile(self.randomFileSidecar):
62             os.remove(self.randomFileSidecar)
63
64
65 ##########################################################################################
66 # The following tests are generic for any FS that supports extended attributes
67 class TestGenericXAttrs(TestXAttrBase):
68
69     def test_xattr_set(self):
70         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
71
72     def test_xattr_get_non_existent(self):
73         key = "user.foo"
74         with self.assertRaises(OSError) as ex:
75             xattr.getxattr(self.randomFile, key)
76         self.assertEqual(ex.exception.errno, ENOATTR)
77
78     def test_xattr_get(self):
79         enc = "utf-8"
80         key = "user.foo"
81         value = "bar"
82         xattr.setxattr(self.randomFile, key, bytes(value, enc))
83         read_value = xattr.getxattr(self.randomFile, key)
84         self.assertEqual(value, read_value.decode(enc))
85
86     # FIXME: On OSX fuse is replacing the return value 0 with -1
87     def test_xattr_set_empty(self):
88         enc = "utf-8"
89         key = "user.foo"
90         value = ""
91         xattr.setxattr(self.randomFile, key, bytes(value, enc))
92         read_value = xattr.getxattr(self.randomFile, key)
93         self.assertEqual(value, read_value.decode(enc))
94
95     def test_xattr_set_override(self):
96         enc = "utf-8"
97         key = "user.foo"
98         value1 = "bar"
99         value2 = "rab"
100         xattr.setxattr(self.randomFile, key, bytes(value1, enc))
101         xattr.setxattr(self.randomFile, key, bytes(value2, enc))
102         read_value = xattr.getxattr(self.randomFile, key)
103         self.assertEqual(value2, read_value.decode(enc))
104
105     def test_xattr_set_create(self):
106         enc = "utf-8"
107         key = "user.foo"
108         value1 = "bar"
109         value2 = "rab"
110         xattr.setxattr(self.randomFile, key, bytes(value1, enc), xattr.XATTR_CREATE)
111         with self.assertRaises(FileExistsError) as ex:
112             xattr.setxattr(self.randomFile, key, bytes(value2, enc), xattr.XATTR_CREATE)
113         self.assertEqual(ex.exception.errno, 17)
114         self.assertEqual(ex.exception.strerror, "File exists")
115
116         read_value = xattr.getxattr(self.randomFile, key)
117         self.assertEqual(value1, read_value.decode(enc))
118
119     def test_xattr_set_replace(self):
120         enc = "utf-8"
121         key = "user.foo"
122         value = "bar"
123         with self.assertRaises(OSError) as ex:
124             xattr.setxattr(self.randomFile, key, bytes(value, enc), xattr.XATTR_REPLACE)
125         self.assertEqual(ex.exception.errno, ENOATTR)
126
127         with self.assertRaises(OSError) as ex:
128             xattr.getxattr(self.randomFile, key)
129         self.assertEqual(ex.exception.errno, ENOATTR)
130
131     def test_xattr_list_empty(self):
132         attrs = xattr.listxattr(self.randomFile)
133         self.assertEqual(len(attrs), 0)
134
135     def test_xattr_list(self):
136         enc = "utf-8"
137         key1 = "user.foo"
138         key2 = "user.foo2"
139         key3 = "user.foo3"
140         value = "bar"
141
142         # set 3 keys
143         xattr.setxattr(self.randomFile, key1, bytes(value, enc))
144         xattr.setxattr(self.randomFile, key2, bytes(value, enc))
145         xattr.setxattr(self.randomFile, key3, bytes(value, enc))
146
147         # get and check the list
148         attrs = xattr.listxattr(self.randomFile)
149
150         self.assertEqual(len(attrs), 3)
151         self.assertTrue(key1 in attrs)
152         self.assertTrue(key2 in attrs)
153         self.assertTrue(key3 in attrs)
154
155     def test_xattr_unicode(self):
156         enc = "utf-8"
157         key = "user.fooシüßてЙĘ𝄠✠"
158         value = "bar"
159
160         # set
161         xattr.setxattr(self.randomFile, key, bytes(value, enc))
162
163         # list
164         attrs = xattr.listxattr(self.randomFile)
165         self.assertEqual(len(attrs), 1)
166         self.assertEqual(attrs[0], key)
167
168         # read
169         read_value = xattr.getxattr(self.randomFile, key)
170         self.assertEqual(value, read_value.decode(enc))
171
172         # remove
173         xattr.removexattr(self.randomFile, key)
174
175     def test_xattr_remove(self):
176         enc = "utf-8"
177         key = "user.foo"
178         value = "bar"
179
180         # set
181         xattr.setxattr(self.randomFile, key, bytes(value, enc))
182
183         # remove
184         xattr.removexattr(self.randomFile, key)
185
186         # list should be empty
187         attrs = xattr.listxattr(self.randomFile)
188         self.assertEqual(len(attrs), 0)
189
190         # should fail when trying to read it
191         with self.assertRaises(OSError) as ex:
192             xattr.getxattr(self.randomFile, key)
193         self.assertEqual(ex.exception.errno, ENOATTR)
194
195         # removing twice should fail
196         with self.assertRaises(OSError) as ex:
197             xattr.getxattr(self.randomFile, key)
198         self.assertEqual(ex.exception.errno, ENOATTR)
199
200     def test_xattr_set_name_max_length(self):
201         max_len = 255
202         if sys.platform != 'linux':
203             max_len = 127   # OSX VFS only support names up to 127 bytes
204
205         enc = "utf-8"
206         key = "user." + "x" * (max_len - 5)
207         value = "x"
208         self.assertEqual(len(key), max_len)
209         xattr.setxattr(self.randomFile, key, bytes(value, enc))  # NOTE: WILL FAIL IF RUNNING ON HFS+
210
211         key = "user." + "x" * (max_len - 5 + 1)
212         self.assertEqual(len(key), max_len + 1)
213         with self.assertRaises(OSError) as ex:
214             xattr.setxattr(self.randomFile, key, bytes(value, enc))
215
216         if sys.platform == 'linux':
217             self.assertEqual(ex.exception.errno, errno.ERANGE)  # This is the behavior of BTRFS
218         else:
219             self.assertEqual(ex.exception.errno, errno.ENAMETOOLONG)
220
221     #########################################################
222     # On Linux: The test fails as the max value is detected before reaching the filesystem
223     # On OSX: Works on fuse_xattr but fails on HFS+ (doesn't has limit)
224     @unittest.skipIf(sys.platform == "linux", "Skipping test on Linux")
225     def test_xattr_set_value_max_size(self):
226         enc = "utf-8"
227         key = "user.foo"
228         value = "x" * (64 * 1024 + 1)  # we want max 64KiB of data
229         with self.assertRaises(OSError) as ex:
230             xattr.setxattr(self.randomFile, key, bytes(value, enc))  # NOTE: This test fail on HFS+ (doesn't have limit)
231
232         self.assertEqual(ex.exception.errno, errno.ENOSPC)
233
234         # on fuse_xattr we get "Argument list too long"
235         # the error is thrown by fuse, not by fuse_xattr code
236
237     #########################################################
238     # Those tests are going to pass on Linux but fail on BSD
239     # BSD doesn't support namespaces.
240     @unittest.skipIf(skipNamespaceTests, "Namespace tests disabled")
241     def test_xattr_set_namespaces(self):
242         with self.assertRaises(OSError) as ex:
243             xattr.setxattr(self.randomFile, "system.foo", bytes("bar", "utf-8"))
244         self.assertEqual(ex.exception.errno, 95)
245         self.assertEqual(ex.exception.strerror, "Operation not supported")
246
247         with self.assertRaises(OSError) as ex:
248             xattr.setxattr(self.randomFile, "trust.foo", bytes("bar", "utf-8"))
249         self.assertEqual(ex.exception.errno, 95)
250         self.assertEqual(ex.exception.strerror, "Operation not supported")
251
252         with self.assertRaises(OSError) as ex:
253             xattr.setxattr(self.randomFile, "foo.foo", bytes("bar", "utf-8"))
254         self.assertEqual(ex.exception.errno, 95)
255         self.assertEqual(ex.exception.strerror, "Operation not supported")
256
257         with self.assertRaises(PermissionError) as ex:
258             xattr.setxattr(self.randomFile, "security.foo", bytes("bar", "utf-8"))
259         self.assertEqual(ex.exception.errno, 1)
260         self.assertEqual(ex.exception.strerror, "Operation not permitted")
261
262
263 ######################################################################
264 # The following tests should fail if they are running on a normal FS
265 class TestFuseXATTR(TestXAttrBase):
266
267     def test_hide_sidecar(self):
268         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
269         self.assertTrue(os.path.isfile(self.randomFile))
270         self.assertFalse(os.path.isfile(self.randomFileSidecar))
271
272         sidecarFilename = self.randomFilename + ".xattr"
273         files_mount = os.listdir(self.mountDir)
274         self.assertTrue(self.randomFilename in files_mount)
275         self.assertTrue(sidecarFilename not in files_mount)
276
277         files_source = os.listdir(self.sourceDir)
278         self.assertTrue(self.randomFilename in files_source)
279         self.assertTrue(sidecarFilename in files_source)
280
281     def test_fuse_xattr_create_new_file(self):
282         test_filename = "test_create_new_file"
283         self.assertFalse(os.path.isfile(self.sourceDir + test_filename))
284         self.assertFalse(os.path.isfile(self.mountDir + test_filename))
285
286         open(self.mountDir + test_filename, "a").close()
287         self.assertTrue(os.path.isfile(self.sourceDir + test_filename))
288         self.assertTrue(os.path.isfile(self.mountDir + test_filename))
289         # FIXME: if one assert fails, the file isn't going to be deleted
290         os.remove(self.mountDir + test_filename)
291
292     def test_fuse_xattr_remove_file_with_sidecar(self):
293         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
294         self.assertTrue(os.path.isfile(self.randomFile))
295         self.assertTrue(os.path.isfile(self.randomSourceFile))
296         self.assertTrue(os.path.isfile(self.randomSourceFileSidecar))
297
298         os.remove(self.randomFile)
299         self.assertFalse(os.path.isfile(self.randomFile))
300         self.assertFalse(os.path.isfile(self.randomSourceFile))
301         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
302
303     def test_fuse_xattr_remove_file_without_sidecar(self):
304         self.assertTrue(os.path.isfile(self.randomFile))
305         self.assertTrue(os.path.isfile(self.randomSourceFile))
306         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
307
308         os.remove(self.randomFile)
309         self.assertFalse(os.path.isfile(self.randomFile))
310         self.assertFalse(os.path.isfile(self.randomSourceFile))
311         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
312
313 if __name__ == '__main__':
314     unittest.main()