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