Better assertEqual() for os.stat(myfile).st_mode

偶尔善良 提交于 2021-02-10 08:44:04

问题


I have a code that checks the st_mode of a file:

self.assertEqual(16877, os.stat(my_directory).st_mode)

Only old school unix experts are able to decipher the integer value 16877 fluently.

Is there more readable way to check for exactly this value?


回答1:


If I may extend the question a bit and understand it as “Is there more readable way to check file modes?”, then I'll suggest adding a custom assertion. The target:

self.assertFileMode(my_directory, user="rwx", group="rx", others="rx")

How to do it.

Let's put that assertion in a mixin:

import os
import stat

class FileAssertions(object):
    FILE_PERMS = {
        'user': {'r': stat.S_IRUSR, 'w': stat.S_IWUSR, 'x': stat.S_IXUSR, 's': stat.S_ISUID},
        'group': {'r': stat.S_IRGRP, 'w': stat.S_IWGRP, 'x': stat.S_IXGRP, 's': stat.S_ISGID},
        'others': {'r': stat.S_IROTH, 'w': stat.S_IWOTH, 'x': stat.S_IXOTH},
    }

    def assertFileMode(self, path, **kwargs):
        mode = os.stat(path).st_mode
        for key, perm_defs in self.FILE_PERMS.items():
            expected = kwargs.pop(key, None)
            if expected is not None:
                actual_perms = mode & sum(perm_defs.values())
                expected_perms = sum(perm_defs[flag] for flag in expected)

                if actual_perms != expected_perms:
                    msg = '{key} permissions: {expected} != {actual} for {path}'.format(
                        key=key, path=path,
                        expected=''.join(sorted(expected)),
                        actual=''.join(sorted(flag for flag, value in perm_defs.items()
                                              if value & mode != 0))
                    )
                    raise self.failureException(msg)
        if kwargs:
            raise TypeError('assertFileMode: unknown arguments %s' % ', '.join(kwargs))

Using it

Now, how about we test some file modes?

# We use our mixin
class MyTestCase(FileAssertions, TestCase):
    def test_some_paths(self):
        # Test all permissions
        self.assertFileMode('/foo/bar', user='rwx', group='rx', others='')

        # Only test user permissions
        self.assertFileMode('/foo/bar', user='rwx')

        # We support the suid/sgid bits as well
        self.assertFileMode('/foo/bar', user='rwxs', group='rxs', others='rx')

Example output:

AssertionError: user permissions: rw != rwx for /foo/bar

Notes:

  • Only permissions given to the method are tested. To test that no permissions exist, pass an empty string.
  • Most of the complexity comes from generating a user-friendly message.
  • Permissions are sorted alphabetically in the error messages, so they are easier to eyeball-compare.
  • To keep it simple, I did not handle testing the sticky bit.



回答2:


You can use defined constats. They can be found in stat (docs). They are:

stat.S_IRUSR for owner read permission,

stat.S_IWUSR for owner write permission,

stat.S_IXUSR for owner execute permission,

stat.S_IRGRP for group read permission,

stat.S_IWGRP for group write permission,

stat.S_IXGRP for group execute permission,

stat.S_IROTH for others read permission,

stat.S_IWOTH for others write permission,

stat.S_IXOTH for others execute permission,

stat.S_IFDIR for directory.

They can be combined using bitwise or |. Then your code can look like:

import stat
import os

permissions = (stat.S_IFDIR | 
               stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
               stat.S_IRGRP | stat.S_IXGRP |
               stat.S_IROTH | stat.S_IXOTH)
self.assertEqual(permissions, os.stat(my_directory).st_mode)



回答3:


Here's some information about st_mode from the manual for stat, quoted below.

       S_IFMT     0170000   bit mask for the file type bit field
       S_IFSOCK   0140000   socket
       S_IFLNK    0120000   symbolic link
       S_IFREG    0100000   regular file
       S_IFBLK    0060000   block device
       S_IFDIR    0040000   directory
       S_IFCHR    0020000   character device
       S_IFIFO    0010000   FIFO
       S_ISUID      04000   set-user-ID bit
       S_ISGID      02000   set-group-ID bit (see below)
       S_ISVTX      01000   sticky bit (see below)
       S_IRWXU      00700   owner has read, write, and execute permission
       S_IRUSR      00400   owner has read permission
       S_IWUSR      00200   owner has write permission
       S_IXUSR      00100   owner has execute permission
       S_IRWXG      00070   group has read, write, and execute permission
       S_IRGRP      00040   group has read permission
       S_IWGRP      00020   group has write permission
       S_IXGRP      00010   group has execute permission
       S_IRWXO      00007   others (not in group) have read, write, and
                            execute permission
       S_IROTH      00004   others have read permission
       S_IWOTH      00002   others have write permission
       S_IXOTH      00001   others have execute permission

These are all octal numbers.

oct(16877) is equivalent to 0o40755. This means that the following bits are active:

  • 40000, or S_IFDIR: directory
  • 700, which means that owner can read, write, and execute
  • 50, which means that the group can read and execute, but not write.
  • 5, which means that the world can read and execute, but not write.

(755 is the mode of the directory.)

Some things you might be able to do:

  1. Link the stat manpage in the documentation, or add the codes I pasted. Then, you can use

    self.assertEqual(0o40755, os.stat(my_directory).st_mode)
    

    instead, which means exactly the same thing, but it becomes easier to debug.

  2. You could use the ctypes module to implement the bitfield yourself. The python wiki has more information. (Search for "Bit field".)
  3. You could put 16877 or 0o40755 (and the other outputs) as the value of a constant variable, perhaps global.

    DIR_755 = 0o40755
    

    Then, you could do:

    self.assertEqual(DIR_755, os.stat(my_directory).st_mode)
    

    to check that the mode is correct.

  4. You could make a dictionary of the codes and values:

    st_mode_vals = {
        "S_IFMT": 0170000,  # Or instead, "filetype_bitmask"
        "S_IFSOCK": 0140000,  # Or instead, "socket"
        ...
        "S_IXOTH": 0o1  # Or instead, "other_x"
    }
    

    You would then be able to define them as:

    DIR_755 = st_mode_vals["directory"] +\
              st_mode_vals["usr_rwx"] +\
              st_mode_vals["grp_r"] + st_mode_vals["grp_x"] +\
              st_mode_vals["oth_r"] + st_mode_vals["oth_x"]
    self.assertEqual(DIR_755, os.stat(my_directory).st_mode)
    

I would personally use #1 (link to documentation), with #3 if there are certain codes that are reused often. For all of them, maybe you can add a comment indicating the value in decimal, too?

edit: I did not see the answer that was added before I posted. I had no idea what the stat module did, so I didn't think of it. Either way, I think I would still use #1 and maybe #3. What he wrote, however, would definitely replace #4 (the dictionary) as another option, and would indeed be much better.




回答4:


self.assertEqual(16877, os.stat(my_directory).st_mode)

Only old school unix experts are able to decipher the integer value 16877 fluently.

Actually, old-school unix experts would probably be in the same boat, since such things are displayed in octal.

I would:

  • switch to octal for specifying the mode
  • add the text representation and a comment

Something like this:

# 'my_directory' should be a directory with full permissions for user and
# read/execute permissions for group and other
# drwxr-xr-x
self.assertEqual(0o40755, os.stat(my_directory).st_mode)


来源:https://stackoverflow.com/questions/32606954/better-assertequal-for-os-statmyfile-st-mode

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!