bugfix: binary_storage: check before writing a key with an empty value. Added test.
[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
17 if xattr.__version__ != '0.9.1':
18     print("WARNING, only tested with xattr version 0.9.1")
19
20 # TODO
21 # - listxattr: list too long
22 # - sidecar file permissions
23 # - corrupt metadata files
24
25
26 class TestXAttrs(unittest.TestCase):
27     def setUp(self):
28         self.sourceDir = "./source/"
29         self.mountDir = "./mount/"
30         self.randomFilename = "foo.txt"
31
32         self.randomFile = self.mountDir + self.randomFilename
33         self.randomFileSidecar = self.randomFile + ".xattr"
34
35         self.randomSourceFile = self.sourceDir + self.randomFilename
36         self.randomSourceFileSidecar = self.randomSourceFile + ".xattr"
37
38         if os.path.isfile(self.randomFile):
39             os.remove(self.randomFile)
40
41         if os.path.isfile(self.randomFileSidecar):
42             os.remove(self.randomFileSidecar)
43
44         Path(self.randomFile).touch()
45         self.assertTrue(os.path.isfile(self.randomFile))
46         self.assertFalse(os.path.isfile(self.randomFileSidecar))
47
48     def tearDown(self):
49         if os.path.isfile(self.randomFile):
50             os.remove(self.randomFile)
51
52         if os.path.isfile(self.randomFileSidecar):
53             os.remove(self.randomFileSidecar)
54
55     def test_xattr_set(self):
56         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
57
58     def test_xattr_set_name_max_length(self):
59         enc = "utf-8"
60         key = "user." + "x" * 250
61         value = "x"
62         self.assertEqual(len(key), 255)
63         xattr.setxattr(self.randomFile, key, bytes(value, enc))
64
65         key = "user." + "x" * 251
66         self.assertEqual(len(key), 256)
67         with self.assertRaises(OSError) as ex:
68             xattr.setxattr(self.randomFile, key, bytes(value, enc))
69         self.assertEqual(ex.exception.errno, 34)
70         self.assertEqual(ex.exception.strerror, "Numerical result out of range")
71
72     @unittest.expectedFailure
73     def test_xattr_set_value_max_size(self):
74         enc = "utf-8"
75         key = "user.foo"
76         value = "x" * (64 * 1024 + 1)  # we want max 64KiB of data
77         with self.assertRaises(OSError) as ex:
78             xattr.setxattr(self.randomFile, key, bytes(value, enc))
79
80         # on btrfs we get "no space left on device"
81         self.assertEqual(ex.exception.errno, 28)
82         self.assertEqual(ex.exception.strerror, "No space left on device")
83
84         # on fuse_xattr we get "Argument list too long"
85         # the error is thrown by fuse, not by fuse_xattr code
86
87     def test_xattr_set_namespaces(self):
88         with self.assertRaises(OSError) as ex:
89             xattr.setxattr(self.randomFile, "system.foo", bytes("bar", "utf-8"))
90         self.assertEqual(ex.exception.errno, 95)
91         self.assertEqual(ex.exception.strerror, "Operation not supported")
92
93         with self.assertRaises(OSError) as ex:
94             xattr.setxattr(self.randomFile, "trust.foo", bytes("bar", "utf-8"))
95         self.assertEqual(ex.exception.errno, 95)
96         self.assertEqual(ex.exception.strerror, "Operation not supported")
97
98         with self.assertRaises(OSError) as ex:
99             xattr.setxattr(self.randomFile, "foo.foo", bytes("bar", "utf-8"))
100         self.assertEqual(ex.exception.errno, 95)
101         self.assertEqual(ex.exception.strerror, "Operation not supported")
102
103         with self.assertRaises(PermissionError) as ex:
104             xattr.setxattr(self.randomFile, "security.foo", bytes("bar", "utf-8"))
105         self.assertEqual(ex.exception.errno, 1)
106         self.assertEqual(ex.exception.strerror, "Operation not permitted")
107
108     def test_xattr_get_non_existent(self):
109         key = "user.foo"
110         with self.assertRaises(OSError) as ex:
111             xattr.getxattr(self.randomFile, key)
112         self.assertEqual(ex.exception.errno, 61)
113         self.assertEqual(ex.exception.strerror, "No data available")
114
115     def test_xattr_get(self):
116         enc = "utf-8"
117         key = "user.foo"
118         value = "bar"
119         xattr.setxattr(self.randomFile, key, bytes(value, enc))
120         read_value = xattr.getxattr(self.randomFile, key)
121         self.assertEqual(value, read_value.decode(enc))
122
123     def test_xattr_set_empty(self):
124         enc = "utf-8"
125         key = "user.foo"
126         value = ""
127         xattr.setxattr(self.randomFile, key, bytes(value, enc))
128         read_value = xattr.getxattr(self.randomFile, key)
129         self.assertEqual(value, read_value.decode(enc))
130
131     def test_xattr_set_override(self):
132         enc = "utf-8"
133         key = "user.foo"
134         value1 = "bar"
135         value2 = "rab"
136         xattr.setxattr(self.randomFile, key, bytes(value1, enc))
137         xattr.setxattr(self.randomFile, key, bytes(value2, enc))
138         read_value = xattr.getxattr(self.randomFile, key)
139         self.assertEqual(value2, read_value.decode(enc))
140
141     def test_xattr_set_create(self):
142         enc = "utf-8"
143         key = "user.foo"
144         value1 = "bar"
145         value2 = "rab"
146         xattr.setxattr(self.randomFile, key, bytes(value1, enc), xattr.XATTR_CREATE)
147         with self.assertRaises(FileExistsError) as ex:
148             xattr.setxattr(self.randomFile, key, bytes(value2, enc), xattr.XATTR_CREATE)
149         self.assertEqual(ex.exception.errno, 17)
150         self.assertEqual(ex.exception.strerror, "File exists")
151
152         read_value = xattr.getxattr(self.randomFile, key)
153         self.assertEqual(value1, read_value.decode(enc))
154
155     def test_xattr_set_replace(self):
156         enc = "utf-8"
157         key = "user.foo"
158         value = "bar"
159         with self.assertRaises(OSError) as ex:
160             xattr.setxattr(self.randomFile, key, bytes(value, enc), xattr.XATTR_REPLACE)
161         self.assertEqual(ex.exception.errno, 61)
162         self.assertEqual(ex.exception.strerror, "No data available")
163
164         with self.assertRaises(OSError) as ex:
165             xattr.getxattr(self.randomFile, key)
166         self.assertEqual(ex.exception.errno, 61)
167         self.assertEqual(ex.exception.strerror, "No data available")
168
169     def test_xattr_list_empty(self):
170         attrs = xattr.listxattr(self.randomFile)
171         self.assertEqual(len(attrs), 0)
172
173     def test_xattr_list(self):
174         enc = "utf-8"
175         key1 = "user.foo"
176         key2 = "user.foo2"
177         key3 = "user.foo3"
178         value = "bar"
179
180         # set 3 keys
181         xattr.setxattr(self.randomFile, key1, bytes(value, enc))
182         xattr.setxattr(self.randomFile, key2, bytes(value, enc))
183         xattr.setxattr(self.randomFile, key3, bytes(value, enc))
184
185         # get and check the list
186         attrs = xattr.listxattr(self.randomFile)
187
188         self.assertEqual(len(attrs), 3)
189         self.assertTrue(key1 in attrs)
190         self.assertTrue(key2 in attrs)
191         self.assertTrue(key3 in attrs)
192
193     def test_xattr_unicode(self):
194         enc = "utf-8"
195         key = "user.fooシüßてЙĘ𝄠✠"
196         value = "bar"
197
198         # set
199         xattr.setxattr(self.randomFile, key, bytes(value, enc))
200
201         # list
202         attrs = xattr.listxattr(self.randomFile)
203         self.assertEqual(len(attrs), 1)
204         self.assertEqual(attrs[0], key)
205
206         # read
207         read_value = xattr.getxattr(self.randomFile, key)
208         self.assertEqual(value, read_value.decode(enc))
209
210         # remove
211         xattr.removexattr(self.randomFile, key)
212
213     def test_xattr_remove(self):
214         enc = "utf-8"
215         key = "user.foo"
216         value = "bar"
217
218         # set
219         xattr.setxattr(self.randomFile, key, bytes(value, enc))
220
221         # remove
222         xattr.removexattr(self.randomFile, key)
223
224         # list should be empty
225         attrs = xattr.listxattr(self.randomFile)
226         self.assertEqual(len(attrs), 0)
227
228         # should fail when trying to read it
229         with self.assertRaises(OSError) as ex:
230             xattr.getxattr(self.randomFile, key)
231         self.assertEqual(ex.exception.errno, 61)
232         self.assertEqual(ex.exception.strerror, "No data available")
233
234         # removing twice should fail
235         with self.assertRaises(OSError) as ex:
236             xattr.getxattr(self.randomFile, key)
237         self.assertEqual(ex.exception.errno, 61)
238         self.assertEqual(ex.exception.strerror, "No data available")
239
240     def test_hide_sidecar(self):
241         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
242         self.assertTrue(os.path.isfile(self.randomFile))
243         self.assertFalse(os.path.isfile(self.randomFileSidecar))
244
245         sidecarFilename = self.randomFilename + ".xattr"
246         files_mount = os.listdir(self.mountDir)
247         self.assertTrue(self.randomFilename in files_mount)
248         self.assertTrue(sidecarFilename not in files_mount)
249
250         files_source = os.listdir(self.sourceDir)
251         self.assertTrue(self.randomFilename in files_source)
252         self.assertTrue(sidecarFilename in files_source)
253
254     def test_create_new_file(self):
255         test_filename = "test_create_new_file"
256         self.assertFalse(os.path.isfile(self.sourceDir + test_filename))
257         self.assertFalse(os.path.isfile(self.mountDir + test_filename))
258
259         open(self.mountDir + test_filename, "a").close()
260         self.assertTrue(os.path.isfile(self.sourceDir + test_filename))
261         self.assertTrue(os.path.isfile(self.mountDir + test_filename))
262         # FIXME: if one assert fails, the file isn't going to be deleted
263         os.remove(self.mountDir + test_filename)
264
265     def test_remove_file_with_sidecar(self):
266         xattr.setxattr(self.randomFile, "user.foo", bytes("bar", "utf-8"))
267         self.assertTrue(os.path.isfile(self.randomFile))
268         self.assertTrue(os.path.isfile(self.randomSourceFile))
269         self.assertTrue(os.path.isfile(self.randomSourceFileSidecar))
270
271         os.remove(self.randomFile)
272         self.assertFalse(os.path.isfile(self.randomFile))
273         self.assertFalse(os.path.isfile(self.randomSourceFile))
274         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
275
276     def test_remove_file_without_sidecar(self):
277         self.assertTrue(os.path.isfile(self.randomFile))
278         self.assertTrue(os.path.isfile(self.randomSourceFile))
279         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
280
281         os.remove(self.randomFile)
282         self.assertFalse(os.path.isfile(self.randomFile))
283         self.assertFalse(os.path.isfile(self.randomSourceFile))
284         self.assertFalse(os.path.isfile(self.randomSourceFileSidecar))
285
286 if __name__ == '__main__':
287     unittest.main()