Define a stop story exception for better error handling

Bug:367714749
Change-Id: I0f2b106c77c8938c266ef75e02abfb480257edbf
Reviewed-on: https://chromium-review.googlesource.com/c/crossbench/+/5913918
Commit-Queue: Suhua Lei <[email protected]>
Reviewed-by: Camillo Bruni <[email protected]>
diff --git a/crossbench/benchmarks/memory/memory_benchmark.py b/crossbench/benchmarks/memory/memory_benchmark.py
index 5345585..8df02b2 100644
--- a/crossbench/benchmarks/memory/memory_benchmark.py
+++ b/crossbench/benchmarks/memory/memory_benchmark.py
@@ -6,8 +6,8 @@
 
 import datetime as dt
 import logging
-import sys
 import time
+
 from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Type, Tuple
 
 import selenium.common.exceptions
@@ -24,6 +24,7 @@
 from crossbench.benchmarks.loading.tab_controller import TabController
 from crossbench.browsers.webdriver import WebDriverBrowser
 from crossbench.parse import NumberParser
+from crossbench.runner.exception import StopStoryException
 
 if TYPE_CHECKING:
   import argparse
@@ -241,11 +242,10 @@
           "return window.performance.timing.navigationStart")
 
       if navigation_starttime != self.navigation_time_ms[handle]:
-        logging.debug("Found a page that has been reloaded!")
         logging.info(
             "The max num of tabs we can keep alive concurrently is: %s ",
             self.tab_count - 1)
-        sys.exit()
+        raise StopStoryException("Found a page that has been reloaded.")
 
   def handle_error(self, e: Exception) -> None:
     """
@@ -256,11 +256,9 @@
     if isinstance(e, selenium.common.exceptions.WebDriverException
                  ) and "page crash" in str(e) or isinstance(
                      e, urllib3.exceptions.ReadTimeoutError):
-      logging.debug("Crash/Timeout found: %s ", e)
       logging.info("The max num of tabs we can keep alive concurrently is: %s ",
                    self.tab_count - 1)
-      # TODO: Check if there is a better way to exit the benchmark.
-      sys.exit()
+      raise StopStoryException(f"Found a Tab Crash/Timeout: {e}")
 
   def handle_page_run(self, run: Run) -> None:
     self._record_navigation_time(run)
diff --git a/crossbench/runner/exception.py b/crossbench/runner/exception.py
new file mode 100644
index 0000000..33ff4fd
--- /dev/null
+++ b/crossbench/runner/exception.py
@@ -0,0 +1,6 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+class StopStoryException(Exception):
+    """Exceptions thrown that resulted in the termination of the story."""
diff --git a/crossbench/runner/run.py b/crossbench/runner/run.py
index dda405f..e3662c8 100644
--- a/crossbench/runner/run.py
+++ b/crossbench/runner/run.py
@@ -17,6 +17,7 @@
 from crossbench.probes.probe_context import ProbeContext
 from crossbench.probes.results import ProbeResultDict
 from crossbench.runner.actions import Actions
+from crossbench.runner.exception import StopStoryException
 from crossbench.runner.probe_context_manager import ProbeContextManager
 from crossbench.runner.result_origin import ResultOrigin
 from crossbench.runner.timing import Timing
@@ -337,8 +338,12 @@
 
   def _run_story(self) -> None:
     self._run_story_setup()
-    self._story.run(self)
-    self._run_story_teardown()
+    try:
+      self._story.run(self)
+    except StopStoryException as e:
+      logging.debug("Stop story: %s", e)
+    finally:
+      self._run_story_teardown()
 
   def _run_story_setup(self) -> None:
     with self.measure("story-setup"):