• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from __future__ import annotations
6
7import logging
8import threading
9from typing import TYPE_CHECKING, Iterable, Tuple
10
11from ordered_set import OrderedSet
12
13from crossbench import exception
14
15if TYPE_CHECKING:
16  from crossbench.browsers.browser import Browser
17  from crossbench.runner.groups.session import BrowserSessionRunGroup
18  from crossbench.runner.run import Run
19  from crossbench.runner.runner import Runner
20
21
22class RunThreadGroup(threading.Thread):
23  """The main interface to start Runs.
24  - Typically only a single RunThreadGroup is used.
25  - If runs are executed in parallel, multiple RunThreadGroup are used
26  """
27
28  def __init__(self, runs: Iterable[Run], index=0, throw: bool = False) -> None:
29    super().__init__()
30    self._index = index
31    self._exceptions = exception.Annotator(throw)
32    self._runs = tuple(runs)
33    assert self._runs, "Got unexpected empty runs list"
34    self._runner: Runner = self._runs[0].runner
35    self._total_run_count = len(self._runner.runs)
36    self._browser_sessions: OrderedSet[BrowserSessionRunGroup] = OrderedSet(
37        run.browser_session for run in self._runs)
38    self.is_dry_run: bool = False
39    self._verify_contains_all_browser_session_runs()
40    self._verify_same_runner()
41    if not self._browser_sessions:
42      raise ValueError("No browser sessions / runs")
43
44  def _verify_contains_all_browser_session_runs(self) -> None:
45    runs_set = set(self._runs)
46    for browser_session in self._browser_sessions:
47      for session_run in browser_session.runs:
48        assert session_run in runs_set, (
49            f"BrowserSession {browser_session} is not allowed to have "
50            f"{session_run} in another RunThreadGroup.")
51
52  def _verify_same_runner(self) -> None:
53    for run in self._runs:
54      assert run.runner is self._runner, "All Runs must have the same Runner."
55
56  @property
57  def index(self) -> int:
58    return self._index
59
60  @property
61  def runner(self) -> Runner:
62    return self._runner
63
64  @property
65  def runs(self) -> Tuple[Run, ...]:
66    return tuple(self._runs)
67
68  @property
69  def browser_sessions(self) -> Tuple[BrowserSessionRunGroup, ...]:
70    return tuple(self._browser_sessions)
71
72  @property
73  def browsers(self) -> Iterable[Browser]:
74    for browser_session in self._browser_sessions:
75      yield browser_session.browser
76
77  @property
78  def exceptions(self) -> exception.Annotator:
79    return self._exceptions
80
81  @property
82  def is_success(self) -> bool:
83    return self._exceptions.is_success
84
85  def _log_run(self, run: Run):
86    logging.info("=" * 80)
87    label = ""
88    if run.is_warmup:
89      label = " | WARMUP, ignoring results"
90    logging.info("RUN %s/%s%s", run.index + 1, self._total_run_count, label)
91    logging.info("��  %s", run.name)
92    logging.info("=" * 80)
93
94  def run(self) -> None:
95    for browser_session in self._browser_sessions:
96      self._run_browser_session(browser_session)
97      if not browser_session.is_success:
98        self._exceptions.extend(browser_session.exceptions)
99    self.runner.exceptions.extend(self._exceptions)
100
101  def _run_browser_session(self,
102                           browser_session: BrowserSessionRunGroup) -> None:
103    if browser_session.is_single_run:
104      self._log_run(browser_session.first_run)
105    else:
106      logging.info("=" * 80)
107    with browser_session.open() as is_success:
108      if not is_success:
109        self._handle_session_startup_failure(browser_session)
110      else:
111        for run in browser_session.runs:
112          self._run_browser_session_run(browser_session, run)
113
114  def _handle_session_startup_failure(
115      self, browser_session: BrowserSessionRunGroup) -> None:
116    runs = tuple(browser_session.runs)
117    logging.info("%s: Skipping %s runs due to browser session setup errors.",
118                 self, len(runs))
119    for run in runs:
120      run.exceptions.extend(browser_session.exceptions)
121
122  def _run_browser_session_run(self, browser_session: BrowserSessionRunGroup,
123                               run: Run) -> None:
124    if not browser_session.is_single_run:
125      self._log_run(run)
126    if not run.is_success:
127      logging.info("%s: Skipping %s due to setup errors.", self, run)
128    else:
129      run.run(self.is_dry_run)
130    if run.is_success:
131      run.log_results()
132    else:
133      browser_session.exceptions.extend(run.exceptions)
134