4 """Unittests for the posix1e module"""
6 # Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop <iustin@k1024.org>
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
41 TEST_DIR = os.environ.get("TEST_DIR", ".")
43 BASIC_ACL_TEXT = "u::rw,g::r,o::-"
45 # This is to workaround python 2/3 differences at syntactic level
46 # (which can't be worked around via if's)
47 M0500 = 320 # octal 0500
48 M0644 = 420 # octal 0644
49 M0755 = 493 # octal 755
51 # Permset permission information
53 posix1e.ACL_READ: ("read", posix1e.Permset.read),
54 posix1e.ACL_WRITE: ("write", posix1e.Permset.write),
55 posix1e.ACL_EXECUTE: ("execute", posix1e.Permset.execute),
59 # Check if running under Python 3
60 IS_PY_3K = sys.hexversion >= 0x03000000
62 def ignore_ioerror(errnum, fn, *args, **kwargs):
63 """Call a function while ignoring some IOErrors.
65 This is needed as some OSes (e.g. FreeBSD) return failure (EINVAL)
66 when doing certain operations on an invalid ACL.
72 err = sys.exc_info()[1]
73 if err.errno == errnum:
78 """Encode a string if needed (under Python 3)"""
86 """Support functions ACLs"""
94 """tear down function"""
95 for fname in self.rmfiles:
97 for dname in self.rmdirs:
101 """create a temp file"""
102 fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR)
103 self.rmfiles.append(fname)
107 """create a temp dir"""
108 dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR)
109 self.rmdirs.append(dname)
112 def _getsymlink(self):
113 """create a symlink"""
114 fh, fname = self._getfile()
117 os.symlink(fname + ".non-existent", fname)
121 class LoadTests(aclTest, unittest.TestCase):
122 """Load/create tests"""
123 def testFromFile(self):
124 """Test loading ACLs from a file"""
125 _, fname = self._getfile()
126 acl1 = posix1e.ACL(file=fname)
127 self.assertTrue(acl1.valid(), "ACL read from file should be valid")
129 def testFromDir(self):
130 """Test loading ACLs from a directory"""
131 dname = self._getdir()
132 acl1 = posix1e.ACL(file=dname)
133 acl2 = posix1e.ACL(filedef=dname)
134 self.assertTrue(acl1.valid(),
135 "ACL read from directory should be valid")
136 # default ACLs might or might not be valid; missing ones are
137 # not valid, so we don't test acl2 for validity
139 def testFromFd(self):
140 """Test loading ACLs from a file descriptor"""
141 fd, _ = self._getfile()
142 acl1 = posix1e.ACL(fd=fd)
143 self.assertTrue(acl1.valid(), "ACL read from fd should be valid")
145 def testFromEmpty(self):
146 """Test creating an empty ACL"""
148 self.assertFalse(acl1.valid(), "Empty ACL should not be valid")
150 def testFromText(self):
151 """Test creating an ACL from text"""
152 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
153 self.assertTrue(acl1.valid(),
154 "ACL based on standard description should be valid")
156 def testFromACL(self):
157 """Test creating an ACL from an existing ACL"""
159 acl2 = posix1e.ACL(acl=acl1)
161 def testInvalidCreationParams(self):
162 """Test that creating an ACL from multiple objects fails"""
163 fd, _ = self._getfile()
164 self.assertRaises(ValueError, posix1e.ACL, text=BASIC_ACL_TEXT, fd=fd)
166 def testInvalidValueCreation(self):
167 """Test that creating an ACL from wrong specification fails"""
168 self.assertRaises(EnvironmentError, posix1e.ACL, text="foobar")
169 self.assertRaises(TypeError, posix1e.ACL, foo="bar")
171 def testDoubleInit(self):
172 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
173 self.assertTrue(acl1.valid())
174 acl1.__init__(text=BASIC_ACL_TEXT)
175 self.assertTrue(acl1.valid())
177 class AclExtensions(aclTest, unittest.TestCase):
178 """ACL extensions checks"""
180 @unittest.skipUnless(HAS_ACL_FROM_MODE, "Missing HAS_ACL_FROM_MODE")
181 def testFromMode(self):
182 """Test loading ACLs from an octal mode"""
183 acl1 = posix1e.ACL(mode=M0644)
184 self.assertTrue(acl1.valid(),
185 "ACL created via octal mode shoule be valid")
187 @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
188 def testAclCheck(self):
189 """Test the acl_check method"""
190 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
191 self.assertFalse(acl1.check(), "ACL is not valid")
193 self.assertTrue(acl2.check(), "Empty ACL should not be valid")
195 @unittest.skipUnless(HAS_EXTENDED_CHECK, "Extended ACL check not supported")
196 def testExtended(self):
197 """Test the acl_extended function"""
198 fd, fname = self._getfile()
199 basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
200 basic_acl.applyto(fd)
201 for item in fd, fname:
202 self.assertFalse(has_extended(item),
203 "A simple ACL should not be reported as extended")
204 enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
205 self.assertTrue(enhanced_acl.valid(),
206 "Failure to build an extended ACL")
207 enhanced_acl.applyto(fd)
208 for item in fd, fname:
209 self.assertTrue(has_extended(item),
210 "An extended ACL should be reported as such")
212 @unittest.skipUnless(HAS_EXTENDED_CHECK, "Extended ACL check not supported")
213 def testExtendedArgHandling(self):
214 self.assertRaises(TypeError, has_extended)
215 self.assertRaises(TypeError, has_extended, object())
217 @unittest.skipUnless(HAS_EQUIV_MODE, "equiv_mode not supported")
218 def testEquivMode(self):
219 """Test the equiv_mode function"""
220 if HAS_ACL_FROM_MODE:
221 for mode in M0644, M0755:
222 acl = posix1e.ACL(mode=mode)
223 self.assertEqual(acl.equiv_mode(), mode)
224 acl = posix1e.ACL(text="u::rw,g::r,o::r")
225 self.assertEqual(acl.equiv_mode(), M0644)
226 acl = posix1e.ACL(text="u::rx,g::-,o::-")
227 self.assertEqual(acl.equiv_mode(), M0500)
229 @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
230 def testToAnyText(self):
231 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
232 self.assertIn(encode("u::"),
233 acl.to_any_text(options=posix1e.TEXT_ABBREVIATE))
234 self.assertIn(encode("user::"), acl.to_any_text())
236 @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
237 def testToAnyTextWrongArgs(self):
238 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
239 self.assertRaises(TypeError, acl.to_any_text, foo="bar")
242 @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported")
243 def testRichCompare(self):
244 acl1 = posix1e.ACL(text="u::rw,g::r,o::r")
245 acl2 = posix1e.ACL(acl=acl1)
246 acl3 = posix1e.ACL(text="u::rw,g::rw,o::r")
247 self.assertEqual(acl1, acl2)
248 self.assertNotEqual(acl1, acl3)
249 self.assertRaises(TypeError, operator.lt, acl1, acl2)
250 self.assertRaises(TypeError, operator.ge, acl1, acl3)
251 self.assertTrue(acl1 != True)
252 self.assertFalse(acl1 == 1)
253 self.assertRaises(TypeError, operator.gt, acl1, True)
255 @unittest.skipUnless(hasattr(posix1e.ACL, "__cmp__"), "__cmp__ is missing")
256 @unittest.skipUnless(__pypy__ is None, "Disabled under pypy")
259 self.assertRaises(TypeError, acl1.__cmp__, acl1)
261 def testApplyToWithWrongObject(self):
262 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
263 self.assertTrue(acl1.valid())
264 self.assertRaises(TypeError, acl1.applyto, object())
265 self.assertRaises(TypeError, acl1.applyto, object(), object())
267 @unittest.skipUnless(HAS_ACL_ENTRY, "ACL entries not supported")
268 def testAclIterator(self):
269 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
270 #self.assertEqual(len(acl), 3)
272 self.assertIs(entry.parent, acl)
275 class WriteTests(aclTest, unittest.TestCase):
278 def testDeleteDefault(self):
279 """Test removing the default ACL"""
280 dname = self._getdir()
281 posix1e.delete_default(dname)
283 @unittest.skipUnless(__pypy__ is None, "Disabled under pypy")
284 def testDeleteDefaultWrongArg(self):
285 self.assertRaises(TypeError, posix1e.delete_default, object())
287 def testReapply(self):
288 """Test re-applying an ACL"""
289 fd, fname = self._getfile()
290 acl1 = posix1e.ACL(fd=fd)
293 dname = self._getdir()
294 acl2 = posix1e.ACL(file=fname)
298 @unittest.skipUnless(HAS_ACL_ENTRY, "ACL entries not supported")
299 class ModificationTests(aclTest, unittest.TestCase):
300 """ACL modification tests"""
302 def checkRef(self, obj):
303 """Checks if a given obj has a 'sane' refcount"""
304 if platform.python_implementation() == "PyPy":
306 ref_cnt = sys.getrefcount(obj)
307 # FIXME: hardcoded value for the max ref count... but I've
308 # seen it overflow on bad reference counting, so it's better
310 if ref_cnt < 2 or ref_cnt > 1024:
311 self.fail("Wrong reference count, expected 2-1024 and got %d" %
315 """Test str() of an ACL."""
316 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
318 self.checkRef(str_acl)
320 def testAppend(self):
321 """Test append a new Entry to the ACL"""
324 e.tag_type = posix1e.ACL_OTHER
325 ignore_ioerror(errno.EINVAL, acl.calc_mask)
327 self.checkRef(str_format)
329 ignore_ioerror(errno.EINVAL, acl.calc_mask)
330 self.assertFalse(acl.valid())
332 def testWrongAppend(self):
333 """Test append a new Entry to the ACL based on wrong object type"""
335 self.assertRaises(TypeError, acl.append, object())
337 def testEntryCreation(self):
339 e = posix1e.Entry(acl)
340 ignore_ioerror(errno.EINVAL, acl.calc_mask)
342 self.checkRef(str_format)
344 def testEntryFailedCreation(self):
345 # Checks for partial initialisation and deletion on error
347 self.assertRaises(TypeError, posix1e.Entry, object())
349 def testDelete(self):
350 """Test delete Entry from the ACL"""
353 e.tag_type = posix1e.ACL_OTHER
354 ignore_ioerror(errno.EINVAL, acl.calc_mask)
356 ignore_ioerror(errno.EINVAL, acl.calc_mask)
358 def testDoubleDelete(self):
359 """Test delete Entry from the ACL"""
360 # This is not entirely valid/correct, since the entry object
361 # itself is invalid after the first deletion, so we're
362 # actually testing deleting an invalid object, not a
363 # non-existing entry...
366 e.tag_type = posix1e.ACL_OTHER
367 ignore_ioerror(errno.EINVAL, acl.calc_mask)
369 ignore_ioerror(errno.EINVAL, acl.calc_mask)
370 self.assertRaises(EnvironmentError, acl.delete_entry, e)
372 # This currently fails as this deletion seems to be accepted :/
373 @unittest.skip("Entry deletion is unreliable")
374 def testDeleteInvalidEntry(self):
375 """Test delete foreign Entry from the ACL"""
379 e.tag_type = posix1e.ACL_OTHER
380 ignore_ioerror(errno.EINVAL, acl1.calc_mask)
381 self.assertRaises(EnvironmentError, acl2.delete_entry, e)
383 def testDeleteInvalidObject(self):
384 """Test delete a non-Entry from the ACL"""
386 self.assertRaises(TypeError, acl.delete_entry, object())
388 def testDoubleEntries(self):
389 """Test double entries"""
390 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
391 self.assertTrue(acl.valid(), "ACL is not valid")
392 for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
395 e.tag_type = tag_type
397 self.assertFalse(acl.valid(),
398 "ACL containing duplicate entries"
399 " should not be valid")
402 def testMultipleGoodEntries(self):
403 """Test multiple valid entries"""
404 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
405 self.assertTrue(acl.valid(), "ACL is not valid")
406 for tag_type in (posix1e.ACL_USER,
408 for obj_id in range(5):
410 e.tag_type = tag_type
414 self.assertTrue(acl.valid(),
415 "ACL should be able to hold multiple"
416 " user/group entries")
418 def testMultipleBadEntries(self):
419 """Test multiple invalid entries"""
420 for tag_type in (posix1e.ACL_USER,
422 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
423 self.assertTrue(acl.valid(), "ACL built from standard description"
426 e1.tag_type = tag_type
430 self.assertTrue(acl.valid(), "ACL should be able to add a"
433 e2.tag_type = tag_type
436 ignore_ioerror(errno.EINVAL, acl.calc_mask)
437 self.assertFalse(acl.valid(), "ACL should not validate when"
438 " containing two duplicate entries")
440 # FreeBSD trips over itself here and can't delete the
441 # entry, even though it still exists.
442 ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
447 e1.tag_type = ACL_USER
453 e2.tag_type = ACL_GROUP
457 self.assertFalse(p2.write)
459 self.assertTrue(p2.write)
460 self.assertEqual(e1.tag_type, e2.tag_type)
462 def testCopyWrongArg(self):
465 self.assertRaises(TypeError, e.copy, object())
467 def testSetPermset(self):
470 e1.tag_type = ACL_USER
476 e2.tag_type = ACL_GROUP
480 self.assertFalse(p2.write)
482 self.assertTrue(e2.permset.write)
483 self.assertEqual(e2.tag_type, ACL_GROUP)
485 def testSetPermsetWrongArg(self):
490 self.assertRaises(TypeError, setter, object())
492 def testPermsetCreation(self):
497 #self.assertEqual(p1, p2)
499 def testPermsetCreationWrongArg(self):
500 self.assertRaises(TypeError, Permset, object())
502 def testPermset(self):
503 """Test permissions"""
509 self.checkRef(str_ps)
510 for perm in PERMSETS:
512 txt = PERMSETS[perm][0]
513 self.checkRef(str_ps)
514 self.assertFalse(ps.test(perm), "Empty permission set should not"
515 " have permission '%s'" % txt)
517 self.assertTrue(ps.test(perm), "Permission '%s' should exist"
518 " after addition" % txt)
520 self.checkRef(str_ps)
522 self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
523 " after deletion" % txt)
525 def testPermsetViaAccessors(self):
526 """Test permissions"""
532 self.checkRef(str_ps)
534 return PERMSETS[perm][1].__get__(ps)
535 def setter(parm, value):
536 return PERMSETS[perm][1].__set__(ps, value)
537 for perm in PERMSETS:
539 self.checkRef(str_ps)
540 txt = PERMSETS[perm][0]
541 self.assertFalse(getter(perm), "Empty permission set should not"
542 " have permission '%s'" % txt)
544 self.assertTrue(ps.test(perm), "Permission '%s' should exist"
545 " after addition" % txt)
546 self.assertTrue(getter(perm), "Permission '%s' should exist"
547 " after addition" % txt)
549 self.checkRef(str_ps)
551 self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
552 " after deletion" % txt)
553 self.assertFalse(getter(perm), "Permission '%s' should not exist"
554 " after deletion" % txt)
556 def testPermsetInvalidType(self):
563 self.assertRaises(TypeError, ps.add, "foobar")
564 self.assertRaises(TypeError, ps.delete, "foobar")
565 self.assertRaises(TypeError, ps.test, "foobar")
566 self.assertRaises(ValueError, setter)
568 @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
569 def testQualifierValues(self):
570 """Tests qualifier correct store/retrieval"""
573 # work around deprecation warnings
574 if hasattr(self, 'assertRegex'):
575 fn = self.assertRegex
577 fn = self.assertRegexpMatches
578 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
582 if tag == posix1e.ACL_USER:
583 regex = re.compile("user with uid %d" % qualifier)
585 regex = re.compile("group with gid %d" % qualifier)
587 e.qualifier = qualifier
588 except OverflowError:
589 # reached overflow condition, break
591 self.assertEqual(e.qualifier, qualifier)
595 @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
596 def testQualifierOverflow(self):
597 """Tests qualifier overflow handling"""
600 qualifier = sys.maxsize * 2
601 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
603 with self.assertRaises(OverflowError):
604 e.qualifier = qualifier
606 @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3")
607 def testNegativeQualifier(self):
608 """Tests negative qualifier handling"""
609 # Note: this presumes that uid_t/gid_t in C are unsigned...
612 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
614 for qualifier in [-10, -5, -1]:
615 with self.assertRaises(OverflowError):
616 e.qualifier = qualifier
618 def testInvalidQualifier(self):
619 """Tests invalid qualifier handling"""
626 self.assertRaises(TypeError, set_qual, object())
627 self.assertRaises((TypeError, AttributeError), del_qual)
629 def testQualifierOnWrongTag(self):
630 """Tests qualifier setting on wrong tag"""
633 e.tag_type = posix1e.ACL_OTHER
638 self.assertRaises(TypeError, set_qual, 1)
639 self.assertRaises(TypeError, get_qual)
642 def testTagTypes(self):
643 """Tests tag type correct set/get"""
646 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
647 posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
650 self.assertEqual(e.tag_type, tag)
651 # check we can show all tag types without breaking
652 self.assertTrue(str(e))
654 def testInvalidTags(self):
655 """Tests tag type incorrect set/get"""
660 self.assertRaises(TypeError, set_tag, object())
663 # For some reason, PyPy raises AttributeError. Strange...
664 self.assertRaises((TypeError, AttributeError), delete_tag)
666 e.tag_type = posix1e.ACL_USER_OBJ
667 tag = max([posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ,
668 posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK,
669 posix1e.ACL_OTHER]) + 1
670 self.assertRaises(EnvironmentError, set_tag, tag)
671 # Check tag is still valid.
672 self.assertEqual(e.tag_type, posix1e.ACL_USER_OBJ)
674 if __name__ == "__main__":