C #: Как проверить исключение StackOverflowException - PullRequest
18 голосов
/ 06 января 2010

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

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

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

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

Ответы [ 7 ]

7 голосов
/ 06 января 2010

Вам нужно будет решить проблему остановки ! Это сделало бы тебя богатым и знаменитым :)

3 голосов
/ 06 января 2010

Как насчет проверки количества кадров в стеке в операторе 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;
    }
}

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

2 голосов
/ 06 января 2010

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

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);
}

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

2 голосов
/ 06 января 2010

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

1 голос
/ 11 декабря 2014

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

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

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

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

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

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

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

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

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

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

1 голос
/ 06 января 2010

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

0 голосов
/ 06 января 2010

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

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

...