• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 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
5import pathlib
6import unittest
7from unittest import mock
8
9import hjson
10
11from crossbench.env import (HostEnvironment, HostEnvironmentConfig,
12                            ValidationError, ValidationMode)
13from tests import test_helper
14from tests.crossbench.base import CrossbenchFakeFsTestCase
15
16
17class HostEnvironmentConfigTestCase(unittest.TestCase):
18
19  def test_combine_bool_value(self):
20    default = HostEnvironmentConfig()
21    self.assertIsNone(default.power_use_battery)
22
23    battery = HostEnvironmentConfig(power_use_battery=True)
24    self.assertTrue(battery.power_use_battery)
25    self.assertTrue(battery.merge(battery).power_use_battery)
26    self.assertTrue(default.merge(battery).power_use_battery)
27    self.assertTrue(battery.merge(default).power_use_battery)
28
29    power = HostEnvironmentConfig(power_use_battery=False)
30    self.assertFalse(power.power_use_battery)
31    self.assertFalse(power.merge(power).power_use_battery)
32    self.assertFalse(default.merge(power).power_use_battery)
33    self.assertFalse(power.merge(default).power_use_battery)
34
35    with self.assertRaises(ValueError):
36      power.merge(battery)
37
38  def test_combine_min_float_value(self):
39    default = HostEnvironmentConfig()
40    self.assertIsNone(default.cpu_min_relative_speed)
41
42    high = HostEnvironmentConfig(cpu_min_relative_speed=1)
43    self.assertEqual(high.cpu_min_relative_speed, 1)
44    self.assertEqual(high.merge(high).cpu_min_relative_speed, 1)
45    self.assertEqual(default.merge(high).cpu_min_relative_speed, 1)
46    self.assertEqual(high.merge(default).cpu_min_relative_speed, 1)
47
48    low = HostEnvironmentConfig(cpu_min_relative_speed=0.5)
49    self.assertEqual(low.cpu_min_relative_speed, 0.5)
50    self.assertEqual(low.merge(low).cpu_min_relative_speed, 0.5)
51    self.assertEqual(default.merge(low).cpu_min_relative_speed, 0.5)
52    self.assertEqual(low.merge(default).cpu_min_relative_speed, 0.5)
53
54    self.assertEqual(high.merge(low).cpu_min_relative_speed, 1)
55
56  def test_combine_max_float_value(self):
57    default = HostEnvironmentConfig()
58    self.assertIsNone(default.cpu_max_usage_percent)
59
60    high = HostEnvironmentConfig(cpu_max_usage_percent=100)
61    self.assertEqual(high.cpu_max_usage_percent, 100)
62    self.assertEqual(high.merge(high).cpu_max_usage_percent, 100)
63    self.assertEqual(default.merge(high).cpu_max_usage_percent, 100)
64    self.assertEqual(high.merge(default).cpu_max_usage_percent, 100)
65
66    low = HostEnvironmentConfig(cpu_max_usage_percent=0)
67    self.assertEqual(low.cpu_max_usage_percent, 0)
68    self.assertEqual(low.merge(low).cpu_max_usage_percent, 0)
69    self.assertEqual(default.merge(low).cpu_max_usage_percent, 0)
70    self.assertEqual(low.merge(default).cpu_max_usage_percent, 0)
71
72    self.assertEqual(high.merge(low).cpu_max_usage_percent, 0)
73
74  def test_parse_example_config_file(self):
75    example_config_file = pathlib.Path(
76        __file__).parent.parent / "config/doc/env.config.hjson"
77    if not example_config_file.exists():
78      raise unittest.SkipTest(f"Test file {example_config_file} does not exist")
79    with example_config_file.open(encoding="utf-8") as f:
80      data = hjson.load(f)
81    HostEnvironmentConfig(**data["env"])
82
83
84class HostEnvironmentTestCase(CrossbenchFakeFsTestCase):
85
86  def setUp(self):
87    super().setUp()
88    self.mock_platform = mock.Mock()
89    self.mock_platform.processes.return_value = []
90    self.out_dir = pathlib.Path("results/current_benchmark_run_results")
91    self.fs.create_file(self.out_dir)
92    self.mock_runner = mock.Mock(
93        platform=self.mock_platform,
94        repetitions=1,
95        probes=[],
96        browsers=[],
97        out_dir=self.out_dir)
98
99  def create_env(self, *args, **kwargs) -> HostEnvironment:
100    return HostEnvironment(self.mock_platform, self.mock_runner.out_dir,
101                           self.mock_runner.browsers, self.mock_runner.probes,
102                           self.mock_runner.repetitions, *args, **kwargs)
103
104  def test_instantiate(self):
105    env = self.create_env()
106    self.assertEqual(env.platform, self.mock_platform)
107
108    config = HostEnvironmentConfig()
109    env = self.create_env(config)
110    self.assertSequenceEqual(env.browsers, self.mock_runner.browsers)
111    self.assertEqual(env.config, config)
112
113  def test_warn_mode_skip(self):
114    config = HostEnvironmentConfig()
115    env = self.create_env(config, ValidationMode.SKIP)
116    env.handle_warning("foo")
117
118  def test_warn_mode_fail(self):
119    config = HostEnvironmentConfig()
120    env = self.create_env(config, ValidationMode.THROW)
121    with self.assertRaises(ValidationError) as cm:
122      env.handle_warning("custom env check warning")
123    self.assertIn("custom env check warning", str(cm.exception))
124
125  def test_warn_mode_prompt(self):
126    config = HostEnvironmentConfig()
127    env = self.create_env(config, ValidationMode.PROMPT)
128    with mock.patch("builtins.input", return_value="Y") as cm:
129      env.handle_warning("custom env check warning")
130    cm.assert_called_once()
131    self.assertIn("custom env check warning", cm.call_args[0][0])
132    with mock.patch("builtins.input", return_value="n") as cm:
133      with self.assertRaises(ValidationError):
134        env.handle_warning("custom env check warning")
135    cm.assert_called_once()
136    self.assertIn("custom env check warning", cm.call_args[0][0])
137
138  def test_warn_mode_warn(self):
139    config = HostEnvironmentConfig()
140    env = self.create_env(config, ValidationMode.WARN)
141    with mock.patch("logging.warning") as cm:
142      env.handle_warning("custom env check warning")
143    cm.assert_called_once()
144    self.assertIn("custom env check warning", cm.call_args[0][0])
145
146  def test_validate_skip(self):
147    env = self.create_env(HostEnvironmentConfig(), ValidationMode.SKIP)
148    env.validate()
149
150  def test_validate_warn(self):
151    env = self.create_env(HostEnvironmentConfig(), ValidationMode.WARN)
152    with mock.patch("logging.warning") as cm:
153      env.validate()
154    cm.assert_not_called()
155    self.mock_platform.sh_stdout.assert_not_called()
156    self.mock_platform.sh.assert_not_called()
157
158  def test_validate_warn_no_probes(self):
159    env = self.create_env(
160        HostEnvironmentConfig(require_probes=True), ValidationMode.WARN)
161    with mock.patch("logging.warning") as cm:
162      env.validate()
163    cm.assert_called_once()
164    self.mock_platform.sh_stdout.assert_not_called()
165    self.mock_platform.sh.assert_not_called()
166
167  def test_request_battery_power_on(self):
168    env = self.create_env(
169        HostEnvironmentConfig(power_use_battery=True), ValidationMode.THROW)
170    self.mock_platform.is_battery_powered = True
171    env.validate()
172
173    self.mock_platform.is_battery_powered = False
174    with self.assertRaises(Exception) as cm:
175      env.validate()
176    self.assertIn("battery", str(cm.exception).lower())
177
178  def test_request_battery_power_off(self):
179    env = self.create_env(
180        HostEnvironmentConfig(power_use_battery=False), ValidationMode.THROW)
181    self.mock_platform.is_battery_powered = True
182    with self.assertRaises(ValidationError) as cm:
183      env.validate()
184    self.assertIn("battery", str(cm.exception).lower())
185
186    self.mock_platform.is_battery_powered = False
187    env.validate()
188
189  def test_request_battery_power_off_conflicting_probe(self):
190    self.mock_platform.is_battery_powered = False
191
192    mock_probe = mock.Mock()
193    mock_probe.configure_mock(BATTERY_ONLY=True, name="mock_probe")
194    self.mock_runner.probes = [mock_probe]
195    env = self.create_env(
196        HostEnvironmentConfig(power_use_battery=False), ValidationMode.THROW)
197
198    with self.assertRaises(ValidationError) as cm:
199      env.validate()
200    message = str(cm.exception).lower()
201    self.assertIn("mock_probe", message)
202    self.assertIn("battery", message)
203
204    mock_probe.BATTERY_ONLY = False
205    env.validate()
206
207  def test_request_is_headless_default(self):
208    env = self.create_env(
209        HostEnvironmentConfig(browser_is_headless=HostEnvironmentConfig.IGNORE),
210        ValidationMode.THROW)
211    mock_browser = mock.Mock(platform=self.mock_platform)
212    self.mock_runner.browsers = [mock_browser]
213
214    mock_browser.viewport.is_headless = False
215    env.validate()
216
217    mock_browser.viewport.is_headless = True
218    env.validate()
219
220  def test_request_is_headless_true(self):
221    mock_browser = mock.Mock(
222        platform=self.mock_platform, path=pathlib.Path("bin/browser_a"))
223    self.mock_runner.browsers = [mock_browser]
224    env = self.create_env(
225        HostEnvironmentConfig(browser_is_headless=True), ValidationMode.THROW)
226
227    self.mock_platform.has_display = True
228    mock_browser.viewport.is_headless = False
229    with self.assertRaises(ValidationError) as cm:
230      env.validate()
231    self.assertIn("is_headless", str(cm.exception))
232
233    self.mock_platform.has_display = False
234    with self.assertRaises(ValidationError) as cm:
235      env.validate()
236
237    self.mock_platform.has_display = True
238    mock_browser.viewport.is_headless = True
239    env.validate()
240
241    self.mock_platform.has_display = False
242    env.validate()
243
244  def test_request_is_headless_false(self):
245    mock_browser = mock.Mock(
246        platform=self.mock_platform, path=pathlib.Path("bin/browser_a"))
247    self.mock_runner.browsers = [mock_browser]
248    env = self.create_env(
249        HostEnvironmentConfig(browser_is_headless=False), ValidationMode.THROW)
250
251    self.mock_platform.has_display = True
252    mock_browser.viewport.is_headless = False
253    env.validate()
254
255    self.mock_platform.has_display = False
256    with self.assertRaises(ValidationError) as cm:
257      env.validate()
258
259    self.mock_platform.has_display = True
260    mock_browser.viewport.is_headless = True
261    with self.assertRaises(ValidationError) as cm:
262      env.validate()
263    self.assertIn("is_headless", str(cm.exception))
264
265  def test_results_dir_single(self):
266    env = self.create_env()
267    with mock.patch("logging.warning") as cm:
268      env.validate()
269    cm.assert_not_called()
270
271  def test_results_dir_non_existent(self):
272    self.mock_runner.out_dir = pathlib.Path("does/not/exist")
273    env = self.create_env()
274    with mock.patch("logging.warning") as cm:
275      env.validate()
276    cm.assert_not_called()
277
278  def test_results_dir_many(self):
279    # Create fake test result dirs:
280    for i in range(30):
281      (self.out_dir.parent / str(i)).mkdir()
282    env = self.create_env()
283    with mock.patch("logging.warning") as cm:
284      env.validate()
285    cm.assert_called_once()
286
287  def test_results_dir_too_many(self):
288    # Create fake test result dirs:
289    for i in range(100):
290      (self.out_dir.parent / str(i)).mkdir()
291    env = self.create_env()
292    with mock.patch("logging.error") as cm:
293      env.validate()
294    cm.assert_called_once()
295
296  def test_check_installed_missing(self):
297
298    def which_none(_):
299      return None
300
301    self.mock_platform.which = which_none
302    env = self.create_env()
303    with self.assertRaises(ValidationError) as cm:
304      env.check_installed(["custom_binary"])
305    self.assertIn("custom_binary", str(cm.exception))
306    with self.assertRaises(ValidationError) as cm:
307      env.check_installed(["custom_binary_a", "custom_binary_b"])
308    self.assertIn("custom_binary_a", str(cm.exception))
309    self.assertIn("custom_binary_b", str(cm.exception))
310
311  def test_check_installed_partially_missing(self):
312
313    def which_custom(binary):
314      if binary == "custom_binary_b":
315        return "/bin/custom_binary_b"
316      return None
317
318    self.mock_platform.which = which_custom
319    env = self.create_env()
320    env.check_installed(["custom_binary_b"])
321    with self.assertRaises(ValidationError) as cm:
322      env.check_installed(["custom_binary_a", "custom_binary_b"])
323    self.assertIn("custom_binary_a", str(cm.exception))
324    self.assertNotIn("custom_binary_b", str(cm.exception))
325
326
327class ValidationModeTestCase(unittest.TestCase):
328
329  def test_construct(self):
330    self.assertIs(ValidationMode("throw"), ValidationMode.THROW)
331    self.assertIs(ValidationMode("THROW"), ValidationMode.THROW)
332    self.assertIs(ValidationMode("prompt"), ValidationMode.PROMPT)
333    self.assertIs(ValidationMode("PROMPT"), ValidationMode.PROMPT)
334    self.assertIs(ValidationMode("warn"), ValidationMode.WARN)
335    self.assertIs(ValidationMode("WARN"), ValidationMode.WARN)
336    self.assertIs(ValidationMode("skip"), ValidationMode.SKIP)
337    self.assertIs(ValidationMode("SKIP"), ValidationMode.SKIP)
338
339
340if __name__ == "__main__":
341  test_helper.run_pytest(__file__)
342