4 """Unittests for the posix1e module"""
6 # Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop <iusty@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
35 TEST_DIR = os.environ.get("TEST_DIR", ".")
37 BASIC_ACL_TEXT = "u::rw,g::r,o::-"
39 # This is to workaround python 2/3 differences at syntactic level
40 # (which can't be worked around via if's)
41 M0500 = 320 # octal 0500
42 M0644 = 420 # octal 0644
43 M0755 = 493 # octal 755
45 # Check if running under Python 3
46 IS_PY_3K = sys.hexversion >= 0x03000000
49 """Wrapper to skip a test"""
50 new_fn = lambda x: None
51 new_fn.__doc__ = "SKIPPED %s" % fn.__doc__
55 def has_ext(extension):
56 """Decorator to skip tests based on platform support"""
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 """Support functions ACLs"""
86 """tear down function"""
87 for fname in self.rmfiles:
89 for dname in self.rmdirs:
93 """create a temp file"""
94 fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR)
95 self.rmfiles.append(fname)
99 """create a temp dir"""
100 dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR)
101 self.rmdirs.append(dname)
104 def _getsymlink(self):
105 """create a symlink"""
106 fh, fname = self._getfile()
109 os.symlink(fname + ".non-existent", fname)
113 class LoadTests(aclTest, unittest.TestCase):
114 """Load/create tests"""
115 def testFromFile(self):
116 """Test loading ACLs from a file"""
117 _, fname = self._getfile()
118 acl1 = posix1e.ACL(file=fname)
119 self.assertTrue(acl1.valid(), "ACL read from file should be valid")
121 def testFromDir(self):
122 """Test loading ACLs from a directory"""
123 dname = self._getdir()
124 acl1 = posix1e.ACL(file=dname)
125 acl2 = posix1e.ACL(filedef=dname)
126 self.assertTrue(acl1.valid(),
127 "ACL read from directory should be valid")
128 # default ACLs might or might not be valid; missing ones are
129 # not valid, so we don't test acl2 for validity
131 def testFromFd(self):
132 """Test loading ACLs from a file descriptor"""
133 fd, _ = self._getfile()
134 acl1 = posix1e.ACL(fd=fd)
135 self.assertTrue(acl1.valid(), "ACL read from fd should be valid")
137 def testFromEmpty(self):
138 """Test creating an empty ACL"""
140 self.assertFalse(acl1.valid(), "Empty ACL should not be valid")
142 def testFromText(self):
143 """Test creating an ACL from text"""
144 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
145 self.assertTrue(acl1.valid(),
146 "ACL based on standard description should be valid")
148 class AclExtensions(aclTest, unittest.TestCase):
149 """ACL extensions checks"""
151 @has_ext(HAS_ACL_FROM_MODE)
152 def testFromMode(self):
153 """Test loading ACLs from an octal mode"""
154 acl1 = posix1e.ACL(mode=M0644)
155 self.assertTrue(acl1.valid(),
156 "ACL created via octal mode shoule be valid")
158 @has_ext(HAS_ACL_CHECK)
159 def testAclCheck(self):
160 """Test the acl_check method"""
161 acl1 = posix1e.ACL(text=BASIC_ACL_TEXT)
162 self.assertFalse(acl1.check(), "ACL is not valid")
164 self.assertTrue(acl2.check(), "Empty ACL should not be valid")
166 @has_ext(HAS_EXTENDED_CHECK)
167 def testExtended(self):
168 """Test the acl_extended function"""
169 fd, fname = self._getfile()
170 basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT)
171 basic_acl.applyto(fd)
172 for item in fd, fname:
173 self.assertFalse(has_extended(item),
174 "A simple ACL should not be reported as extended")
175 enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r")
176 self.assertTrue(enhanced_acl.valid(),
177 "Failure to build an extended ACL")
178 enhanced_acl.applyto(fd)
179 for item in fd, fname:
180 self.assertTrue(has_extended(item),
181 "An extended ACL should be reported as such")
183 @has_ext(HAS_EQUIV_MODE)
184 def testEquivMode(self):
185 """Test the equiv_mode function"""
186 if HAS_ACL_FROM_MODE:
187 for mode in M0644, M0755:
188 acl = posix1e.ACL(mode=mode)
189 self.assertEqual(acl.equiv_mode(), mode)
190 acl = posix1e.ACL(text="u::rw,g::r,o::r")
191 self.assertEqual(acl.equiv_mode(), M0644)
192 acl = posix1e.ACL(text="u::rx,g::-,o::-")
193 self.assertEqual(acl.equiv_mode(), M0500)
196 class WriteTests(aclTest, unittest.TestCase):
199 def testDeleteDefault(self):
200 """Test removing the default ACL"""
201 dname = self._getdir()
202 posix1e.delete_default(dname)
204 def testReapply(self):
205 """Test re-applying an ACL"""
206 fd, fname = self._getfile()
207 acl1 = posix1e.ACL(fd=fd)
210 dname = self._getdir()
211 acl2 = posix1e.ACL(file=fname)
215 class ModificationTests(aclTest, unittest.TestCase):
216 """ACL modification tests"""
218 def checkRef(self, obj):
219 """Checks if a given obj has a 'sane' refcount"""
220 if platform.python_implementation() == "PyPy":
222 ref_cnt = sys.getrefcount(obj)
223 # FIXME: hardcoded value for the max ref count... but I've
224 # seen it overflow on bad reference counting, so it's better
226 if ref_cnt < 2 or ref_cnt > 1024:
227 self.fail("Wrong reference count, expected 2-1024 and got %d" %
231 """Test str() of an ACL."""
232 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
234 self.checkRef(str_acl)
236 @has_ext(HAS_ACL_ENTRY)
237 def testAppend(self):
238 """Test append a new Entry to the ACL"""
241 e.tag_type = posix1e.ACL_OTHER
242 ignore_ioerror(errno.EINVAL, acl.calc_mask)
244 self.checkRef(str_format)
246 @has_ext(HAS_ACL_ENTRY)
247 def testDelete(self):
248 """Test delete Entry from the ACL"""
251 e.tag_type = posix1e.ACL_OTHER
252 ignore_ioerror(errno.EINVAL, acl.calc_mask)
254 ignore_ioerror(errno.EINVAL, acl.calc_mask)
256 @has_ext(HAS_ACL_ENTRY)
257 def testDoubleEntries(self):
258 """Test double entries"""
259 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
260 self.assertTrue(acl.valid(), "ACL is not valid")
261 for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ,
264 e.tag_type = tag_type
266 self.assertFalse(acl.valid(),
267 "ACL containing duplicate entries"
268 " should not be valid")
271 @has_ext(HAS_ACL_ENTRY)
272 def testMultipleGoodEntries(self):
273 """Test multiple valid entries"""
274 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
275 self.assertTrue(acl.valid(), "ACL is not valid")
276 for tag_type in (posix1e.ACL_USER,
278 for obj_id in range(5):
280 e.tag_type = tag_type
284 self.assertTrue(acl.valid(),
285 "ACL should be able to hold multiple"
286 " user/group entries")
288 @has_ext(HAS_ACL_ENTRY)
289 def testMultipleBadEntries(self):
290 """Test multiple invalid entries"""
291 for tag_type in (posix1e.ACL_USER,
293 acl = posix1e.ACL(text=BASIC_ACL_TEXT)
294 self.assertTrue(acl.valid(), "ACL built from standard description"
297 e1.tag_type = tag_type
301 self.assertTrue(acl.valid(), "ACL should be able to add a"
304 e2.tag_type = tag_type
307 ignore_ioerror(errno.EINVAL, acl.calc_mask)
308 self.assertFalse(acl.valid(), "ACL should not validate when"
309 " containing two duplicate entries")
311 # FreeBSD trips over itself here and can't delete the
312 # entry, even though it still exists.
313 ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
315 @has_ext(HAS_ACL_ENTRY)
316 def testPermset(self):
317 """Test permissions"""
323 self.checkRef(str_ps)
325 posix1e.ACL_READ: "read",
326 posix1e.ACL_WRITE: "write",
327 posix1e.ACL_EXECUTE: "execute",
331 self.checkRef(str_ps)
332 self.assertFalse(ps.test(perm), "Empty permission set should not"
333 " have permission '%s'" % pmap[perm])
335 self.assertTrue(ps.test(perm), "Permission '%s' should exist"
336 " after addition" % pmap[perm])
338 self.checkRef(str_ps)
340 self.assertFalse(ps.test(perm), "Permission '%s' should not exist"
341 " after deletion" % pmap[perm])
344 @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
345 def testQualifierValues(self):
346 """Tests qualifier correct store/retrieval"""
349 # work around deprecation warnings
350 if hasattr(self, 'assertRegex'):
351 fn = self.assertRegex
353 fn = self.assertRegexpMatches
354 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
358 if tag == posix1e.ACL_USER:
359 regex = re.compile("user with uid %d" % qualifier)
361 regex = re.compile("group with gid %d" % qualifier)
363 e.qualifier = qualifier
364 except OverflowError:
365 # reached overflow condition, break
367 self.assertEqual(e.qualifier, qualifier)
371 @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
372 def testQualifierOverflow(self):
373 """Tests qualifier overflow handling"""
376 qualifier = sys.maxsize * 2
377 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
379 with self.assertRaises(OverflowError):
380 e.qualifier = qualifier
382 @has_ext(HAS_ACL_ENTRY and IS_PY_3K)
383 def testNegativeQualifier(self):
384 """Tests negative qualifier handling"""
385 # Note: this presumes that uid_t/gid_t in C are unsigned...
388 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
390 for qualifier in [-10, -5, -1]:
391 with self.assertRaises(OverflowError):
392 e.qualifier = qualifier
395 if __name__ == "__main__":