1# Copyright 2024 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 abc 8import contextlib 9import logging 10from collections.abc import Generator 11from typing import TYPE_CHECKING, Iterable, Tuple 12 13from crossbench import plt 14from crossbench.helper import DurationMeasureContext, Durations 15from crossbench.probes.result_location import ResultLocation 16 17if TYPE_CHECKING: 18 from crossbench.browsers.browser import Browser 19 from crossbench.exception import (Annotator, ExceptionAnnotationScope, 20 TExceptionTypes) 21 from crossbench.path import AnyPath, LocalPath 22 from crossbench.probes.probe import Probe 23 from crossbench.runner.runner import Runner 24 25 26class ResultOrigin(abc.ABC): 27 """Base class for Run and BrowserSession, both places where 28 probe results can be placed.""" 29 30 @property 31 def is_local(self) -> bool: 32 return self.browser_platform.is_local 33 34 @property 35 def is_remote(self) -> bool: 36 return self.browser_platform.is_remote 37 38 @property 39 @abc.abstractmethod 40 def browser_tmp_dir(self) -> AnyPath: 41 pass 42 43 @property 44 @abc.abstractmethod 45 def out_dir(self) -> LocalPath: 46 pass 47 48 @property 49 @abc.abstractmethod 50 def exceptions(self) -> Annotator: 51 pass 52 53 @property 54 @abc.abstractmethod 55 def durations(self) -> Durations: 56 pass 57 58 @property 59 @abc.abstractmethod 60 def browser(self) -> Browser: 61 pass 62 63 @property 64 def runner(self) -> Runner: 65 raise NotImplementedError( 66 f"Cannot access on runner on {type(self).__name__}") 67 68 @property 69 def host_platform(self) -> plt.Platform: 70 return self.browser.host_platform 71 72 @property 73 def browser_platform(self) -> plt.Platform: 74 return self.browser.platform 75 76 @property 77 def probes(self) -> Iterable[Probe]: 78 # TODO: migrate away from using runner 79 return self.runner.probes 80 81 @contextlib.contextmanager 82 def measure( 83 self, label: str 84 ) -> Generator[Tuple[ExceptionAnnotationScope, DurationMeasureContext], None, 85 None]: 86 # Return a combined context manager that adds an named exception info 87 # and measures the time during the with-scope. 88 with self.exceptions.info(label) as stack, self.durations.measure( 89 label) as timer: 90 yield (stack, timer) 91 92 def exception_info(self, *stack_entries: str) -> ExceptionAnnotationScope: 93 return self.exceptions.info(*stack_entries) 94 95 def exception_handler( 96 self, *stack_entries: str, exceptions: TExceptionTypes = (Exception,) 97 ) -> ExceptionAnnotationScope: 98 return self.exceptions.capture(*stack_entries, exceptions=exceptions) 99 100 def get_default_probe_result_path(self, probe: Probe) -> AnyPath: 101 """Return a local or remote/browser-based result path depending on the 102 Probe default RESULT_LOCATION.""" 103 if probe.RESULT_LOCATION == ResultLocation.BROWSER: 104 return self.get_browser_probe_result_path(probe) 105 if probe.RESULT_LOCATION == ResultLocation.LOCAL: 106 return self.get_local_probe_result_path(probe) 107 raise ValueError(f"Invalid probe.RESULT_LOCATION {probe.RESULT_LOCATION} " 108 f"for probe {probe}") 109 110 @abc.abstractmethod 111 def get_local_probe_result_path(self, probe: Probe) -> LocalPath: 112 pass 113 114 def get_browser_probe_result_path(self, probe: Probe) -> AnyPath: 115 local_path = self.get_local_probe_result_path(probe) 116 if self.is_local: 117 return local_path 118 # Create a temp file relative to the remote browser tmp dir. 119 relative_path = local_path.relative_to(self.out_dir) 120 path = self.browser_tmp_dir / relative_path 121 logging.debug("Creating remote result dir=%s on platform=%s", path.parent, 122 self.browser_platform) 123 self.browser_platform.mkdir(path.parent) 124 return path 125