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