C#:如何测试 StackOverflowException

发布于 2024-08-17 03:28:46 字数 737 浏览 3 评论 0原文

假设您有一个方法可能会陷入无限的方法调用循环并因 StackOverflowException 崩溃。例如,这个问题中提到的我天真的RecursiveSelect方法< /a>.

从 .NET Framework 2.0 版开始,try-catch 块无法捕获 StackOverflowException 对象,并且默认情况下会终止相应的进程。因此,建议用户编写代码来检测和防止堆栈溢出。例如,如果您的应用程序依赖于递归,请使用计数器或状态条件来终止递归循环。

获取该信息(来自这个答案< /a>) 考虑到,由于无法捕获异常,是否有可能为这样的事情编写测试?或者,如果测试失败,是否会真正破坏整个测试套件?

注意:我知道我可以尝试一下,看看会发生什么,但我对有关它的一般信息更感兴趣。比如,不同的测试框架和测试运行程序会以不同的方式处理这个问题吗?即使有可能,我是否应该避免这样的测试?

Say you have a method that could potentially get stuck in an endless method-call loop and crash with a StackOverflowException. For example my naive RecursiveSelect method mentioned in this question.

Starting with the .NET Framework version 2.0, a StackOverflowException object cannot be caught by a try-catch block and the corresponding process is terminated by default. Consequently, users are advised to write their code to detect and prevent a stack overflow. For example, if your application depends on recursion, use a counter or a state condition to terminate the recursive loop.

Taking that information (from this answer) into account, since the exception can't be caught, is it even possible to write a test for something like this? Or would a test for this, if that failed, actually break the whole test-suite?

Note: I know I could just try it out and see what happens, but I am more interested in general information about it. Like, would different test frameworks and test runners handle this differently? Should I avoid a test like this even though it might be possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

北风几吹夏 2024-08-24 03:28:46

您需要解决停止问题!这会让你变得富有和出名:)

You would need to solve the Halting Problem! That would get you rich and famous :)

盛装女皇 2024-08-24 03:28:46

在断言语句中检查堆栈上的帧数怎么样?

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

在相关问题的示例中,这将是(昂贵的断言语句将在发布版本中删除):

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

但这不是一般测试,因为您需要预期它会发生,而且您只能检查帧数而不能堆栈的实际大小。也不可能检查另一个调用是否会超出堆栈空间,您所能做的就是粗略估计堆栈上总共有多少个调用。

How about checking the number of frames on the stack in an assert statement?

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

In your example from the related question this would be (The costly assert statement would be removed in the release build):

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

It's not a general test though, as you need to expect it to happen, plus you can only check the frame count and not the actual size of the stack. It is also not possible to check whether another call will exceed stack space, all that you can do is roughly estimate how many calls in total will fit on your stack.

梅窗月明清似水 2024-08-24 03:28:46

这是邪恶的,但你可以在新的进程中旋转它。从单元测试启动该过程并等待其完成并检查结果。

It's evil but you can spin it up in a new process. Launch the process from the unit test and wait for it to complete and check the result.

前事休说 2024-08-24 03:28:46

这个想法是跟踪递归函数的嵌套深度,这样它就不会使用太多的堆栈空间。示例:

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

这当然不能完全保护您免受堆栈溢出的影响,因为可以在开始时剩余的堆栈空间太少的情况下调用该方法,但它可以确保该方法不会单独导致堆栈溢出。

The idea is to keep track of how deeply a recursive funcion is nested, so that it doesn't use too much stack space. Example:

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

This of course can't totally protect you from stack overflows, as the method can be called with too little stack space left to start with, but it makes sure that the method doesn't singelhandedly cause a stack overflow.

浅紫色的梦幻 2024-08-24 03:28:46

我们无法对 StackOverflow 进行测试,因为这是当没有更多堆栈可供分配时的情况,应用程序将在这种情况下自动退出。

We cannot have a test for StackOverflow because this is the situation when there is no more stack left for allocation, the application would exit automatically in this situation.

半透明的墙 2024-08-24 03:28:46

如果您正在编写其他人要使用的库代码,则堆栈溢出往往比其他错误更严重,因为其他代码不能仅仅吞掉 StackOverflowException ;他们的整个过程正在下降。

没有简单的方法来编写期望并捕获 StackOverflowException 的测试,但这不是您想要测试的行为!

以下是一些测试代码不会堆栈溢出的提示:

  • 并行化您的测试运行。如果您有一个单独的测试套件来处理堆栈溢出情况,那么即使一个测试运行程序出现故障,您仍然可以获得其他测试的结果。 (即使您不分离测试运行,我也认为堆栈溢出非常严重,如果发生这种情况,值得使整个测试运行程序崩溃。您的代码一开始就不应该中断!)

  • 线程可能有不同数量的堆栈空间,如果有人调用您的代码,您无法控制有多少堆栈空间可用。虽然 32 位 CLR 的默认堆栈大小为 1MB,64 位 CLR 的默认堆栈大小为 2MB,但请注意 Web 服务器将默认为更小的堆栈。您的测试代码可以使用 Thread 之一 如果您想验证代码不会在可用空间较少的情况下使用较小堆栈大小的构造函数。

  • 测试您提供的每种不同的构建风格(发布和调试?带或不带调试符号?x86 和 x64 和 AnyCPU?)以及您将支持的平台(64 位?32 位?运行 32 位的 64 位操作系统?。 NET 4.5?3.5?)。生成的本机代码中堆栈帧的实际大小可能不同,因此一台机器上的中断可能不会在另一台机器上中断。

  • 由于您的测试可能在一台构建机器上通过,但在另一台构建机器上失败,请确保如果它开始失败,它不会阻止整个项目的签入!

  • 一旦测量了导致代码堆栈溢出的迭代次数 N 的次数,不要只测试这个数字!我会测试更大数量(50 N?)的迭代不会堆栈溢出(因此您不会得到在一个构建服务器上通过但在另一台构建服务器上失败的测试)。

  • 考虑函数最终可以调用自身的每个可能的代码路径。您的产品可能会阻止 X() -> X()-> X()-> ... 递归堆栈溢出,但是如果有 X() -> A()-> B()-> C()-> ...-> W()-> X()-> A()-> ... 仍然有问题的递归情况?

PS:我没有关于此策略的更多详细信息,但显然如果您的代码 托管 CLR 那么您可以指定堆栈溢出仅使 AppDomain 崩溃吗?

If you are writing library code that somebody else is going to use, stack overflows tends to be a lot worse than other bugs because the other code can't just swallow the StackOverflowException; their entire process is going down.

There's no easy way to write a test that expects and catches a StackOverflowException, but that's not the behavior you want to be testing!

Here's some tips for testing your code doesn't stack overflow:

  • Parallelize your test runs. If you have a separate test suite for the stack overflow cases, then you'll still get results from the other tests if one test runner goes down. (Even if you don't separate your test runs, I'd consider stack overflows to be so bad that it's worth crashing the whole test runner if it happens. Your code shouldn't break in the first place!)

  • Threads may have different amounts of stack space, and if somebody is calling your code you can't control how much stack space is available. While the default for 32 bit CLR is 1MB stack size and 64 bit is 2MB stack size, be aware that web servers will default to a much smaller stack. Your test code could use one of the Thread constructors that takes a smaller stack size if you want to verify your code won't stack overflow with less avaliable space.

  • Test every different build flavor that you ship (Release and Debug? with or without debugging symbols? x86 and x64 and AnyCPU?) and platforms you'll support (64 bit? 32 bit? 64 bit OS running 32 bit? .NET 4.5? 3.5? mono?). The actual size of the stack frame in the generated native code could be different, so what breaks on one machine might not break on another.

  • Since your tests might pass on one build machine but fail on another, ensure that if it starts failing it doesn't block checkins for your entire project!

  • Once you measure how few iterations N cause your code to stack overflow, don't just test that number! I'd test a much larger number (50 N?) of iterations doesn't stack overflow (so you don't get tests that pass on one build server but fail on another).

  • Think about every possible code path where a function can eventually later call itself. Your product might prevent the X() -> X() -> X() -> ... recursive stack overflow, but what if there is a X() -> A() -> B() -> C() -> ... -> W() -> X() -> A() -> ... recursive case that is still broken?

PS: I don't have more details about this strategy, but apparently if your code hosts the CLR then you can specify that stack overflow only crashes the AppDomain?

断爱 2024-08-24 03:28:46

首先也是最重要的,我认为该方法应该处理这个问题并确保它不会递归得太深。

完成此检查后,我认为应该通过暴露达到的最大深度并断言它永远不会大于检查允许的深度来测试检查(如果有的话)。

First and foremost I think the method should handle this and make sure it does not recurse too deep.

When this check is done, I think the check should be tested - if anything - by exposing the maximum depth reached and assert it is never larger than the check allows.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文