Подтвердить что ты не робот

С#: как проверить для StackOverflowException

Скажем, у вас есть метод, который потенциально может застрять в бесконечном цикле вызовов метода и сбой с помощью StackOverflowException. Например, мой наивный метод RecursiveSelect, упомянутый в этот вопрос.

Начиная с .NET Framework версии 2.0 объект StackOverflowException не может быть захвачен блоком try-catch, и соответствующий процесс по умолчанию завершается. Следовательно, пользователям рекомендуется написать свой код для обнаружения и предотвращения. Например, если ваше приложение зависит от рекурсии, используйте счетчик или условие состояния для завершения рекурсивного цикла.

Принимая эту информацию (от этот ответ), поскольку исключение нельзя поймать, можно ли даже написать тест на что-то вроде этого? Или будет тест на это, если это не удастся, на самом деле сломать весь тестовый пакет?

Примечание: Я знаю, что могу просто попробовать и посмотреть, что произойдет, но меня больше интересует общая информация об этом. Вроде бы, разные рамки тестирования и тестовые бегуны справятся с этим по-другому? Должен ли я избегать такого теста, даже если это возможно?

4b9b3361

Ответ 2

Как проверить количество кадров в стеке в инструкции assert?

const int MaxFrameCount = 100000;
Debug.Assert(new StackTrace().FrameCount < MaxFrameCount);

В вашем примере из связанного с этим вопроса это было бы (в сборке релизов будет снят дорогостоящий оператор assert):

public static IEnumerable<T> SelectRecursive<T>(this IEnumerable<T> subjects, Func<T, IEnumerable<T>> selector)
{
    const int MaxFrameCount = 100000;
    Debug.Assert(new StackTrace().FrameCount < MaxFrameCount);

    // Stop if subjects are null or empty
    if(subjects == null || !subjects.Any())
        yield break;

    // For each subject
    foreach(var subject in subjects)
    {
        // Yield it
        yield return subject;

        // Then yield all its decendants
        foreach (var decendant in SelectRecursive(selector(subject), selector))
            yield return decendant;
    }
}

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

Ответ 3

Это зло, но вы можете развернуть его в новом процессе. Запустите процесс из unit test и дождитесь его завершения и проверьте результат.

Ответ 4

Идея состоит в том, чтобы отслеживать, насколько глубоко вложен рекурсивный funcion, так что он не использует слишком много пространства стека. Пример:

string ProcessString(string s, int index) {
   if (index > 1000) return "Too deeply nested";
   s = s.Substring(0, index) + s.Substring(index, 1).ToUpper() + s.Substring(index + 1);
   return ProcessString(s, index + 1);
}

Это, конечно же, не может полностью защитить вас от, так как метод можно вызвать с слишком небольшим количеством стека, оставшимся для начала, но он гарантирует, что метод не может привести к переполнению стека.

Ответ 5

У нас не может быть теста для StackOverflow, потому что это та ситуация, когда больше нет остатка для распределения, приложение автоматически выйдет в этой ситуации.

Ответ 6

Если вы пишете библиотечный код, который кто-то еще собирается использовать, переполнение стека имеет тенденцию быть намного хуже, чем другие ошибки, потому что другой код не может просто усвоить StackOverflowException; весь процесс идет вниз.

Нет простого способа написать тест, который ожидает и ловит StackOverflowException, но это не то поведение, которое вы хотите тестировать!

Здесь некоторые советы по тестированию вашего кода не переполняют стек:

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

  • В потоках может быть разное количество пространства стека, и если кто-то вызывает ваш код, вы не можете контролировать, сколько пространства стека доступно. Хотя значение по умолчанию для 32-битного CLR составляет размер 1 МБ, а бит 64 бит - размер стека 2 МБ, помните, что веб-сервер будет по умолчанию намного меньше стека, Ваш тестовый код может использовать один из конструкторов Thread, который занимает меньший размер стека, если вы хотите проверить, что ваш код не будет переполнять поток менее доступное пространство.

  • Протестируйте каждый другой аромат сборки, который вы отправляете (отпустите и отлаживайте? с или без отладки символов? x86 и x64 и AnyCPU?) и поддерживаемых вами платформ (64-разрядная 32-разрядная 64-разрядная ОС под управлением 32 бит?.NET 4.5? 3.5? mono?). Фактический размер фрейма стека в сгенерированном собственном коде может быть другим, поэтому разрывы на одной машине могут не ломаться на другой.

  • Поскольку ваши тесты могут проходить на одной машине сборки, но сбой на другой, убедитесь, что если она начнет сбой, она не блокирует проверки для всего вашего проекта!

  • Как только вы измеряете, как мало итераций N вызывают переполнение стека кода, не просто проверяйте этот номер! Я бы тестировал гораздо большее число (50 N?) Итераций, не переполнял переполнение стека (поэтому вы не получаете тестов, которые проходят на одном сервере сборки, но терпят неудачу на другом).

  • Подумайте о каждом возможном пути кода, в котором функция в конечном итоге может вызвать себя. Ваш продукт может помешать рекурсивному переполнению стека X() -> X() -> X() -> ..., но что, если есть рекурсивный случай X() -> A() -> B() -> C() -> ... -> W() -> X() -> A() -> ..., который все еще сломан?

PS: У меня нет более подробной информации об этой стратегии, но, видимо, если ваш код размещает CLR, вы можете указать, что переполнение стека только сбой AppDomain?

Ответ 7

Прежде всего, я думаю, что метод должен справиться с этим и убедиться, что он не слишком усложняется.

Когда эта проверка завершена, я думаю, что проверка должна быть проверена - если что угодно - разоблачая достигнутую максимальную глубину и утверждая, что она никогда не больше, чем позволяет проверка.