🐞

eventでリストを表示させる

2022/07/03に公開

Hypothesisを使って次のような形で event で自作クラスのインスタンスのリストを表示させたいとする。

from dataclasses import dataclass
from hypothesis import event, given
from hypothesis.strategies import builds, integers, lists, text

@dataclass(frozen=True)
class Foo:
    x: int
    y: str

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        return hash((self.x, self.y))

@given(lists(builds(Foo, integers(), text()), max_size=4))
def test_foo(l):
    event(l)

すると次のようにeventはunhashableなオブジェクトは扱えない旨のエラーメッセージが出る。

self = ConjectureData(VALID, 1 bytes, frozen), event = []

    def note_event(self, event: Hashable) -> None:
        assert isinstance(self.events, set)
>       self.events.add(event)
E       TypeError: unhashable type: 'list'

というわけで、リストのサイズも小さいのでタプルにすることでhashableにする。

@given(lists(builds(Foo, integers(), text()), max_size=4))
def test_foo(l):
    t = tuple(l)
    event(t)

しかし今度は次のようなエラーで落ちる。

self = <WeakKeyDictionary at 0x105c17430>, key = ()

    def __getitem__(self, key):
>       return self.data[ref(key)]
E       TypeError: cannot create weak reference to 'tuple' object

../../../.pyenv/versions/3.10.4/lib/python3.10/weakref.py:416: TypeError

本当はコードの中を見たほうがいいと思うんだけど、event でタプルを受け取るなんて普通にやることだと思うから、ワークアラウンドがあるんじゃなかと思って、Stack Overflowに質問してみた。

すると、Hypothesisのリードメンテナーから回答が得られて、これはバグとして処理するとのこと。一応ワークアラウンドとして 'str' で対応することが提案された。

@given(lists(builds(Foo, integers(), text()), max_size=3))
def test_foo(l):
    s = str(l)
    event(s)

これは無事に実行できた。

============================== Hypothesis Statistics ===============================

test_foo.py::test_foo:

  - during generate phase (0.15 seconds):
    - Typical runtimes: 0-1 ms, ~ 79% in data generation
    - 100 passing examples, 0 failing examples, 26 invalid examples
    - Events:
      * 9.52%, [Foo(x=0, y='')]
      * 2.38%, [Foo(x=-37, y='')]
      * 1.59%, [Foo(x=-88, y='\x0ff')]
      * 1.59%, [Foo(x=22754, y=''), Foo(x=514, y=' ')]
      * 1.59%, [Foo(x=22754, y='4 ')]
      * 0.79%, [Foo(x=-108, y='^!P\U000a3497\U000e0e57\x84Óñtª\x88\x95\U000a788dLE'), Foo(x=-109, y='\x94`§O\U00045d57'), Foo(x=18695, y='\U000e40840Xë£\U00019dfcì\U0004dd2e²÷¢\U0004f407×\U0007a1bd\x9eÖ')]
      (...略...)
      * 0.79%, [Foo(x=8720, y='ý'), Foo(x=-4009, y='`\U000c599cÉ\U000ff3f6\U0004e5e2Ç\U00014b48\U000aee32Ï')]
      * 0.79%, []

  - Stopped because settings.max_examples=100

一応リードメンテナーがバグだと言っていたので、upstreamにもissueとして報告しておいた。

追記 (2022-07-04)

本記事の内容はupstream(ver 6.48.3)で対応されたのでobsoleteになりました。今後はhashableを投げれば普通に表示されます。

Discussion