** Я перешел от среды. Новый адрес kodare.net **

Недавно мы выполнили профилирование нашего конвейера пакетной обработки и обнаружили, что метод __hash__ класса Frozen(часть tri.struct) занимает довольно много времени. Это было удивительно во многих отношениях:

  1. Мы не думали, что использовали его много
  2. Он не должен много хешировать, потому что он кеширует результаты.
  3. Весь пакет tri.struct тестируется на мутации с нулевым выжившим мутантом (используя mutmut).

После некоторого просмотра того, откуда он был вызван, мы увидели, что пункт 1 был неверным предположением. Мы действительно много использовали его косвенно через FrozenStruct, а затем Token (из tri.token). Но это статичные структуры, в основном перечисления на стероидах. Они должны хэшироваться один раз, а затем выполняться, но у нас были миллионы обращений к __hash__, а не сотни. Очевидно, что предположение 2 неверно, и мутационное тестирование не выявило его. Код выглядит следующим образом:

def __hash__(self):
    hash_key = '_hash'  # pragma: no mutate
    try:
        _hash = self[hash_key]
    except KeyError:
        _hash = hash(
            tuple((k, self[k]) 
            for k in sorted(self.keys())))
        dict.__setattr__(self, hash_key, _hash)
    return _hash

Так как это реализация замороженного объекта, то есть объекта, который должен быть неизменяемым, мы не можем просто использовать settattr как обычно, поэтому мы проделываем странный маленький танец явного вызова dict.__setattr__. Проблема здесь заключается в получении значения внутри попытки. Это всегда будет вызывать KeyError! Таким образом, изменение _hash = self[hash_key] на _hash = self[None] не изменит поведение.

Исправление довольно простое: используйте dict.__getattribute__ вместо self[ (и ловите AttributeError вместо KeyError).

Более важный и интересный вопрос: почему мутационное тестирование не обнаружило этого? Оказывается, просто не было мутации с a[b] на a[None]. Но теперь есть! Выпущенный сегодня Mutmut 1.0 содержит эту новую мутацию. Повторный запуск тестов мутации в предыдущем коде tri.struct находит этот мутант, и когда мы исправим ошибку и добавим для нее тест, мутант будет уничтожен.

Обновление: после множества проблем мне наконец удалось запустить эту ошибку с помощью двух других тестеров мутаций Python Cosmic Ray и mutpy. Ни один из них не находит этот мутант по состоянию на 2018–11–23 гг.