Лучше assertEqual() для os.stat(myfile).st_mode

У меня есть код, который проверяет st_mode файла:

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

Только эксперты старой школы Unix могут свободно расшифровать целочисленное значение 16877.

Есть ли более читаемый способ проверить именно это значение?


person guettli    schedule 16.09.2015    source источник
comment
Возможно, в зависимости от того, что вы подразумеваете под читаемым. Код, который у вас есть в вашем вопросе, скрывает много вещей за кулисами. Например: os.stat(my_directory) возвращает объект os.stat_return(), который имеет несколько свойств, включая st_mode. Что касается демистификации 16877, вы можете присвоить значение переменной с соответствующим именем, но это все, что вы можете сделать, поскольку свойство st_mode объекта os.stat_return() всегда будет числовым значением.   -  person Alea Kootz    schedule 21.09.2015
comment
Ваша система устанавливает и проверяет это состояние? У вас есть функция, которая проверяет состояние? Не могли бы вы использовать это в утверждении? У вас есть имя для этого состояния в вашей системе (например, только для владельца)?   -  person Peter Wood    schedule 23.09.2015
comment
@PeterWood да, моя система устанавливает это состояние. Да, он проверяет это состояние. Это строка выше. Нет функции проверить это. Но тест, который содержит указанную выше строку. Названия до сих пор нет. Но хорошая идея. Название для государства было бы еще лучше. Спасибо   -  person guettli    schedule 23.09.2015
comment
Пожалуйста. О проверке: я не имею в виду ваши тесты, я имею в виду, проверяет ли ваша система, например. до или после выполнения какой-либо операции. Как система к этому относится? Я знаю, что это 0o40755 или 16877, но если дать ему повсеместное название, это поможет всем.   -  person Peter Wood    schedule 23.09.2015
comment
@PeterWood До сих пор я бы называл это umask по умолчанию.   -  person guettli    schedule 23.09.2015


Ответы (4)


Если я могу немного расширить вопрос и понять его как «Есть ли более читаемый способ проверки режимов файлов?», то я предлагаю добавить пользовательское утверждение. Цель:

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

Как это сделать.

Давайте поместим это утверждение в миксин:

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))

Использование

Теперь, как насчет того, чтобы протестировать некоторые файловые режимы?

# 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')

Пример вывода:

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

Примечания:

  • Проверяются только разрешения, предоставленные методу. Чтобы проверить отсутствие разрешений, передайте пустую строку.
  • Большая часть сложности связана с созданием удобного для пользователя сообщения.
  • Разрешения отсортированы в алфавитном порядке в сообщениях об ошибках, поэтому их легче сравнивать на глаз.
  • Для простоты я не занимался тестированием закрепляющего бита.
person spectras    schedule 26.09.2015
comment
Да, это отличное решение для повторного использования. Было бы неплохо иметь его как устанавливаемую библиотеку pip. - person guettli; 27.09.2015
comment
Спасибо. Я считаю, что действительно есть место для пакета python, который добавил бы кучу полезных — можно сказать семантических — утверждений, особенно о манипулировании файлами и содержимым, временных каталогах… Сделать это правильно с надлежащей документацией и тщательным тестированием слишком сложно. стараюсь для себя в данный момент хотя. - person spectras; 27.09.2015
comment
Кстати, настоящим я помещаю код в своем ответе в общественное достояние. Делайте с ним все, что хотите. Кредит приветствуется, но не является обязательным. - person spectras; 27.09.2015

Вы можете использовать определенные константы. Их можно найти в stat (документы). Они есть:

stat.S_IRUSR для разрешения владельца на чтение,

stat.S_IWUSR для разрешения владельца на запись,

stat.S_IXUSR для разрешения владельца на выполнение,

stat.S_IRGRP для разрешения на чтение группы,

stat.S_IWGRP для группового разрешения на запись,

stat.S_IXGRP для разрешения на групповое выполнение,

stat.S_IROTH для других разрешений на чтение,

stat.S_IWOTH для других разрешение на запись,

stat.S_IXOTH для других разрешение на выполнение,

stat.S_IFDIR для каталога.

Их можно комбинировать с помощью побитового или |. Тогда ваш код может выглядеть так:

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)
person sbeliakov    schedule 21.09.2015

Вот некоторая информация о st_mode из руководства для stat, цитируется ниже.

       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

Это все восьмеричные числа.

oct(16877) эквивалентно 0o40755. Это означает, что активны следующие биты:

  • 40000 или S_IFDIR: каталог
  • 700, что означает, что владелец может читать, писать и выполнять
  • 50, что означает, что группа может читать и выполнять, но не писать.
  • 5, что означает, что мир может читать и выполнять, но не писать.

(755 — это режим каталога.)

Некоторые вещи, которые вы можете сделать:

  1. Ссылка на справочную страницу stat в документации или добавление кодов, которые я вставил. Затем вы можете использовать

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

    вместо этого, что означает то же самое, но становится легче отлаживать.

  2. Вы можете использовать модуль ctypes для самостоятельной реализации битового поля. Дополнительную информацию можно найти в вики Python. (Найдите «Битовое поле».)
  3. Вы можете поместить 16877 или 0o40755 (и другие выходные данные) в качестве значения постоянной переменной, возможно, глобальной.

    DIR_755 = 0o40755
    

    Затем вы можете сделать:

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

    чтобы убедиться, что режим правильный.

  4. Вы можете сделать словарь кодов и значений:

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

    Затем вы сможете определить их как:

    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)
    

Я бы лично использовал № 1 (ссылка на документацию) и № 3, если есть определенные коды, которые часто используются повторно. Для всех них, может быть, вы тоже можете добавить комментарий, указывающий значение в десятичной форме?

edit: я не видел ответа, который был добавлен до того, как я опубликовал. Я понятия не имел, что делает модуль stat, поэтому не подумал об этом. В любом случае, я думаю, что я бы все равно использовал № 1 и, возможно, № 3. Однако то, что он написал, определенно заменит № 4 (словарь) в качестве еще одного варианта и действительно будет намного лучше.

person Jean Nassar    schedule 21.09.2015
comment
Спасибо за ваше мнение и предоставленные варианты. Однако я хотел бы поделиться некоторыми мыслями по этому поводу. Обычно использование библиотечных констант является лучшим подходом, чем определение ваших. Вот почему: во-первых, зачем переопределять то, что уже существует? Во-вторых, если что-то изменится (представим, что в новой версии unix появились новые константы разрешений, или os.stat начал использовать свой формат, а не unix), то в случае системных/библиотечных констант код все равно будет работать, но если использовать ваши собственные константы, то вам придется изменить их в коде или, что еще хуже, вы пропустите их, и вам будет трудно найти ошибки или утечки безопасности. - person sbeliakov; 21.09.2015
comment
Это то, что я не учел. Я думал, что вариант №1 был лучшим, потому что он показался мне наиболее понятным для большинства программистов, по сравнению с S_IFDIR и т. д. Это также причина, по которой я дал альтернативные имена в словаре. Но я вижу вашу точку зрения в ремонтопригодности и уступаю. Спасибо. - person Jean Nassar; 21.09.2015

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

Только эксперты старой школы Unix могут свободно расшифровать целочисленное значение 16877.

На самом деле, эксперты старой школы Unix, вероятно, были бы в той же лодке, поскольку такие вещи отображаются в восьмеричном формате.

Я бы:

  • переключиться на восьмеричное для указания режима
  • добавить текстовое представление и комментарий

Что-то вроде этого:

# '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)
person Ethan Furman    schedule 21.09.2015