Frage C #: So testen Sie StackOverflowException


Angenommen, Sie haben eine Methode, die möglicherweise in einer endlosen Methodenaufrufschleife stecken bleibt und mit einer StackOverflowException abstürzt. Zum Beispiel meine Naive RecursiveSelect Methode erwähnt in diese Frage.

Ab Version .NET Framework 2.0 kann ein StackOverflowException-Objekt nicht von einem try-catch-Block abgefangen werden und der entsprechende Prozess wird standardmäßig beendet. Daher wird Benutzern empfohlen, ihren Code zu schreiben, um einen Stack-Überlauf zu erkennen und zu verhindern. Wenn Ihre Anwendung beispielsweise von Rekursion abhängig ist, verwenden Sie einen Zähler oder eine Statusbedingung, um die rekursive Schleife zu beenden.

Diese Information nehmen (von diese Antwort), da die Ausnahme nicht abgefangen werden kann, ist es überhaupt möglich, einen Test für so etwas zu schreiben? Oder würde ein Test dafür, wenn das fehlgeschlagen wäre, tatsächlich die ganze Testsuite durchbrechen?

Hinweis: Ich weiß, ich könnte es einfach ausprobieren und sehen, was passiert, aber ich interessiere mich mehr für allgemeine Informationen darüber. Wie würden unterschiedliche Test-Frameworks und Test-Runner das anders handhaben? Sollte ich einen solchen Test vermeiden, obwohl es möglich ist?


18
2018-01-06 11:40


Ursprung


Antworten:


Du müsstest das lösen Problem anhalten! Das würde dich reich und berühmt machen :)


7
2018-01-06 11:52



Wie wäre es mit der Überprüfung der Anzahl der Frames auf dem Stack in einer assert-Anweisung?

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

In Ihrem Beispiel aus der verwandten Frage wäre dies (Die kostspielige Assert-Anweisung würde im Release-Build entfernt):

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

Es ist jedoch kein allgemeiner Test, wie Sie es erwarten müssen, und Sie können nur die Frame-Anzahl und nicht die tatsächliche Größe des Stacks überprüfen. Es ist auch nicht möglich, zu überprüfen, ob ein anderer Aufruf den Stack-Speicherplatz überschreitet. Alles, was Sie tun können, ist ungefähr abzuschätzen, wie viele Aufrufe insgesamt auf Ihren Stack passen.


3
2018-01-06 11:48



Es ist böse, aber du kannst es in einem neuen Prozess aufmischen. Starten Sie den Prozess aus dem Komponententest und warten Sie, bis er abgeschlossen ist, und überprüfen Sie das Ergebnis.


2
2018-01-06 11:53



Die Idee besteht darin, zu verfolgen, wie tief eine rekursive Funktion verschachtelt ist, so dass sie nicht zu viel Stapelraum verbraucht. Beispiel:

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

Dies kann natürlich nicht vollständig vor Stack-Überläufen schützen, da die Methode mit zu wenig Stack-Speicherplatz gestartet werden kann, aber es stellt sicher, dass die Methode nicht einen Stack-Overflow verursacht.


2
2018-01-06 12:00



Wir können keinen Test für StackOverflow durchführen, da dies der Fall ist, wenn kein Stapel mehr für die Zuweisung vorhanden ist. In diesem Fall wird die Anwendung automatisch beendet.


1
2018-01-06 11:49



Wenn Sie Bibliothekscode schreiben, den jemand anderes verwenden wird, ist der Stapelüberlauf tendenziell um einiges schlechter als bei anderen Fehlern, weil der andere Code nicht einfach den Code schlucken kann StackOverflowException; ihr gesamter Prozess geht zurück.

Es gibt keine einfache Möglichkeit, einen Test zu schreiben, der ein erwartet und fängt StackOverflowException, aber das ist nicht das Verhalten, das du testen willst!

Hier finden Sie einige Tipps zum Testen Ihres Codes nicht Paketüberfluss:

  • Parallelisieren Sie Ihre Testläufe. Wenn Sie eine separate Testsuite für die Stack-Überlauffälle haben, erhalten Sie immer noch Ergebnisse aus den anderen Tests, wenn ein Test Runner ausfällt. (Auch wenn Sie Ihre Testläufe nicht trennen, würde ich Stack-Überläufe als so schlecht bezeichnen, dass es sich lohnt, den gesamten Test-Runner zum Absturz zu bringen. Ihr Code sollte nicht an erster Stelle brechen!)

  • Threads können unterschiedliche Stapelspeicherplätze haben, und wenn jemand Ihren Code aufruft, können Sie nicht steuern, wie viel Stack-Speicherplatz verfügbar ist. Während der Standardwert für 32-Bit-CLR 1 MB Stapelgröße und 64 Bit 2 MB Stapelgröße ist, beachten Sie, dass Webserver werden standardmäßig auf einen viel kleineren Stack gesetzt. Ihr Testcode könnte einen der folgenden verwenden Thread Konstruktoren, die eine kleinere Stapelgröße verwenden, wenn Sie Ihren Code überprüfen möchten, stapeln keinen Überlauf mit weniger verfügbarem Speicherplatz.

  • Testen Sie jeden Build-Flavour, den Sie bereitstellen (Release und Debug? Mit oder ohne Debugging-Symbolen? X86 und x64 und AnyCPU?) Und Plattformen, die Sie unterstützen (64 Bit? 32 Bit? 64 Bit OS mit 32 Bit? .NET 4.5? 3,5? Mono?). Die tatsächliche Größe des Stack-Frames im generierten nativen Code kann unterschiedlich sein. Daher können die Breaks auf einem Computer möglicherweise nicht unterbrochen werden.

  • Da Ihre Tests möglicherweise einen Build-Computer weitergeben, aber auf einem anderen fehlschlagen, stellen Sie sicher, dass der Check-in für das gesamte Projekt nicht blockiert wird, wenn der Test fehlschlägt.

  • Sobald Sie messen, wie wenige Iterationen N verursachen Sie Ihren Code, um Überlauf zu stapeln, prüfen Sie nicht gerade diese Zahl! Ich würde eine viel größere Anzahl testen (50 N?) von Iterationen stapelt keinen Überlauf (also erhalten Sie keine Tests, die einen Build-Server weitergeben, aber auf einem anderen fehlschlagen).

  • Denken Sie über jeden möglichen Codepfad nach, an dem sich eine Funktion später selbst nennen kann. Ihr Produkt könnte das verhindern X() -> X() -> X() -> ... rekursiver Stapelüberlauf, aber was ist, wenn es einen gibt? X() -> A() -> B() -> C() -> ... -> W() -> X() -> A() -> ... rekursiver Fall, der noch gebrochen ist?

PS: Ich habe nicht mehr Details über diese Strategie, aber anscheinend, wenn Ihr Code hostet die CLR dann können Sie angeben, dass der Stapelüberlauf nur die AppDomain abstürzt?


1
2017-12-11 17:40



In erster Linie denke ich, dass die Methode damit umgehen und sicherstellen sollte, dass sie nicht zu tief in den Hintergrund tritt.

Wenn diese Überprüfung abgeschlossen ist, denke ich, dass die Überprüfung - wenn überhaupt - getestet werden sollte, indem die maximale Tiefe, die erreicht wurde, ausgesetzt wird und behauptet, dass sie niemals größer ist, als die Prüfung erlaubt.


0
2018-01-06 12:01