BETA

HaskellでのQuickCheckとData.ByteString.Char8の罠

投稿日:2018-10-23
最終更新:2018-10-24

URL変換を伴う部分でQuickCheckで以下のようなコードをテストしていたときです。

-- SBS = Data.ByteString.Char8
spec :: Spec
spec = do
  prop " urlEncode/urlDecode" $
    \t -> t == (SBS.unpack . urlDecode . urlEncode . SBS.pack) t

こんな失敗が出ました。

test/Network/Api/UrlSpec.hs:20:3: 
  18) Network.Api.Url  urlEncode/urlDecode
       Falsifiable (after 3 tests and 1 shrink):
         "\883916"

\883916という値で失敗しているようです。ちょっとghciでチェックしてみましょう。

Prelude Network.HTTP.Types.URI> urlDecode $ urlEncode "\883916"
"\204"

もとの数から変化している……?これはなんとなくピンときました。内部でUTF-32を使っているのでエスケープしたとき\x10ffffまで取ることができるのです。 UTF-32 - Wikipedia で、ByteStringは内部的には[Word8]ですからこれをこの文字(\883916)をWord8に突っ込もうとします。もちろん255までしか突っ込めません。するとどうなるのかと言うと、なんとHaskell側で勝手に下位8ビット以外捨ててしまい無理やり0x00~0xffの範囲に収めてしまうのです。

それはともかくなぜこんなことが起こったのか……。ByteStringChar8バージョンなのがミソでしたQuickCheckはテストデータとして任意のStringは作ってくれますが、任意のByteStringは作ってくれません。[Word8]なら任意のデータを作ってくれるのでそれをpackしてByteStringを作っていました。

そしてどこかのタイミングで「おっ、このData.ByteString.Char8って便利じゃーん」とか思いそれに変えました。Data.ByteString.Char8packString -> ByteString、つまりQuickCheckでテストデータを生成しようとするとUTF-32[Char]が生成され、それが[Word8]にまとめられる……というわけでした。

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@kirisakiの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう