blob: 353e44ae5b2bfb541c7ae07376134777abf99166 [file] [log] [blame]
# 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.
from __future__ import annotations
import abc
import contextlib
import datetime as dt
import io
import logging
import pathlib
from typing import Final, List, Optional, Sequence, Tuple
from unittest import mock
import crossbench
from crossbench import path as pth
from crossbench import plt
from crossbench.benchmarks.loading.loadline_presets import \
LoadLineTabletBenchmark
from crossbench.browsers.browser import Browser
from crossbench.browsers.settings import Settings
from crossbench.cli.cli import CrossBenchCLI
from crossbench.cli.config.browser_variants import BrowserVariantsConfig
from crossbench.cli.config.network import NetworkConfig
from crossbench.cli.config.secrets import SecretsConfig
from pyfakefs import fake_filesystem_unittest
from tests import test_helper
from tests.crossbench import mock_browser
from tests.crossbench.mock_helper import MockCLI, MockPlatform
class CrossbenchFakeFsTestCase(
fake_filesystem_unittest.TestCase, metaclass=abc.ABCMeta):
def setUp(self) -> None:
super().setUp()
self.setUpPyfakefs(modules_to_reload=[crossbench, mock_browser, pth])
# gettext is used extensively in argparse
gettext_patcher = mock.patch(
"gettext.dgettext", side_effect=lambda domain, message: message)
gettext_patcher.start()
self.addCleanup(gettext_patcher.stop)
sleep_patcher = mock.patch('time.sleep', return_value=None)
sleep_patcher.start()
self.addCleanup(sleep_patcher.stop)
def create_file(self,
path_str: str,
contents: Optional[str] = None) -> pathlib.Path:
path = pathlib.Path(path_str)
self.fs.create_file(path, contents=contents)
return path
class BaseCrossbenchTestCase(CrossbenchFakeFsTestCase, metaclass=abc.ABCMeta):
def filter_splashscreen_urls(self, urls: Sequence[str]) -> List[str]:
return [url for url in urls if not url.startswith("data:")]
def setUp(self) -> None:
# Instantiate MockPlatform before setting up fake_filesystem so we can
# still interact with the original, real plt.Platform object for extracting
# basic system information.
self.platform = MockPlatform() # pytype: disable=not-instantiable
super().setUp()
self._default_log_level = logging.getLogger().getEffectiveLevel()
logging.getLogger().setLevel(logging.CRITICAL)
for mock_browser_cls in mock_browser.ALL:
mock_browser_cls.setup_fs(self.fs)
self.assertTrue(mock_browser_cls.mock_app_path().exists())
self.out_dir = pathlib.Path("/tmp/results/test")
self.out_dir.parent.mkdir(parents=True)
self.fs.add_real_directory(
LoadLineTabletBenchmark.default_network_config_path().parent,
lazy_read=(not test_helper.is_google_env()))
if test_helper.is_google_env():
self.fs.add_real_directory("/build/cas")
self.browsers: List[mock_browser.MockBrowser] = [
mock_browser.MockChromeDev(
"dev", settings=Settings(platform=self.platform)),
mock_browser.MockChromeStable(
"stable", settings=Settings(platform=self.platform))
]
mock_platform_patcher = mock.patch.object(plt, "PLATFORM", self.platform)
mock_platform_patcher.start()
self.addCleanup(mock_platform_patcher.stop)
for browser in self.browsers:
self.assertListEqual(browser.expected_js, [])
self.mock_args = mock.Mock(
wraps=False,
driver_path=None,
network_config=None,
browser_config=None,
viewport=None,
splash_screen=None,
secrets=SecretsConfig(),
wipe_system_user_data=False,
http_request_timeout=dt.timedelta(),
cache_dir=pathlib.Path("test_cache_dir"),
enable_features=None,
disable_features=None,
js_flags=None,
enable_field_trial_config=False,
network=NetworkConfig.default(),
probe=[],
other_browser_args=[],
driver_logging=False)
def tearDown(self) -> None:
logging.getLogger().setLevel(self._default_log_level)
self.assertListEqual(self.platform.sh_results, [])
super().tearDown()
class SysExitTestException(Exception):
def __init__(self, exit_code=0):
super().__init__("sys.exit")
self.exit_code = exit_code
class BaseCliTestCase(BaseCrossbenchTestCase):
SPLASH_URLS_LEN: Final[int] = 2
def setUp(self) -> None:
super().setUp()
# tabulate and textwrap can be slow for tests, let's mock them out.
def mock_tabulate(table, *args, **kwargs):
del args, kwargs
return str(table)
patcher = mock.patch("tabulate.tabulate", side_effect=mock_tabulate)
self.addCleanup(patcher.stop)
patcher.start()
def mock_wrap(text, *args, **kwargs):
del args, kwargs
return [text]
patcher = mock.patch("textwrap.wrap", side_effect=mock_wrap)
self.addCleanup(patcher.stop)
patcher.start()
def run_cli_output(self,
*args,
raises=None,
enable_logging: bool = True) -> Tuple[MockCLI, str, str]:
with mock.patch(
"sys.stdout", new_callable=io.StringIO) as mock_stdout, mock.patch(
"sys.stderr", new_callable=io.StringIO) as mock_stderr:
cli = self.run_cli(*args, raises=raises, enable_logging=enable_logging)
stdout = mock_stdout.getvalue()
stderr = mock_stderr.getvalue()
# Make sure we don't accidentally reuse the buffers across run_cli calls.
mock_stdout.close()
mock_stderr.close()
return cli, stdout, stderr
def run_cli(self,
*args,
raises=None,
enable_logging: bool = False) -> MockCLI:
cli = MockCLI(platform=self.platform, enable_logging=enable_logging)
with mock.patch(
"sys.exit", side_effect=SysExitTestException), mock.patch.object(
plt, "PLATFORM", self.platform):
if raises:
with self.assertRaises(raises):
cli.run(args)
else:
cli.run(args)
return cli
def mock_chrome_stable(self):
return mock.patch.object(
BrowserVariantsConfig,
"_get_browser_cls",
return_value=mock_browser.MockChromeStable)
@contextlib.contextmanager
def patch_get_browser(self, return_value: Optional[Sequence[Browser]] = None):
if not return_value:
return_value = self.browsers
with mock.patch.object(
CrossBenchCLI, "_get_browsers", return_value=return_value):
yield