Expecto FsCheck получает исключение переполнения стека при генерации строки

Я пытаюсь научиться правильно использовать FsCheck и в данный момент интегрирую его с Expecto. Я могу запустить тесты свойств, если использую конфигурацию FsCheck по умолчанию, но когда я пытаюсь использовать свой собственный генератор, это вызывает исключение переполнения стека.

вот мой генератор

type NameGen() =
    static member Name() =
        Arb.generate<string * string>
        |> Gen.where (fun (firstName, lastName) ->
            firstName.Length > 0 && lastName.Length > 0
        )
        |> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
        |> Arb.fromGen
        |> Arb.convert string id

И я пытаюсь использовать это так:

let config = { FsCheckConfig.defaultConfig with arbitrary = [typeof<NameGen>] }

let propertyTests input =
    let output = toInitials input
    output.EndsWith(".")

testPropertyWithConfig config "Must end with period" propertyTests

Исключение выбрасывается еще до того, как оно попадет в функцию Gen.where

Что я делаю не так? Спасибо


person kaeedo    schedule 28.06.2017    source источник


Ответы (2)


Вы пытаетесь использовать генератор строк FsCheck, чтобы переопределить, как работает его генератор строк, но когда вы это сделаете, он будет рекурсивно вызывать себя до тех пор, пока не закончится место в стеке. Это известная проблема: https://github.com/fscheck/FsCheck/issues/109

Эта альтернатива работает?

type NameGen =
    static member Name () =
        Arb.Default.NonEmptyString().Generator
        |> Gen.map (fun (NonEmptyString s) -> s)
        |> Gen.two
        |> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
        |> Arb.fromGen
person Mark Seemann    schedule 28.06.2017

Вы определяете новый генератор для строки типа, но в нем вы используете генератор для string * string, который использует генератор для string. FsCheck, к сожалению, хранит генераторы в глобальном изменяемом состоянии (возможно, не без оснований?), и я думаю, что это означает, что генератор продолжает вызывать себя до тех пор, пока не переполнится стек.

Вы можете решить эту проблему, определив генератор для пользовательского типа оболочки вместо простой строки (показано ниже).

Следующей проблемой, с которой вы столкнетесь, будут исключения с нулевыми ссылками. Первоначально сгенерированная строка может быть null, и вы пытаетесь получить доступ к свойству .Length. Это можно решить с помощью функции String.length, которая возвращает 0 вместо null.

С этими изменениями ваш генератор выглядит так:

type Name = Name of string

type NameGen() =
    static member Name() =
        Arb.generate<string * string>
        |> Gen.where (fun (firstName, lastName) ->
            String.length firstName > 0 && String.length lastName > 0
        )
        |> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
        |> Arb.fromGen
        |> Arb.convert Name (fun (Name n) -> n)

И ваша собственность нуждается в небольшой модификации:

let propertyTests (Name input) =
    let output = toInitials input
    output.EndsWith(".")
person TheQuickBrownFox    schedule 28.06.2017