1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Provides fakes for several of Telemetry's internal objects. 6 7These allow code like story_runner and Benchmark to be run and tested 8without compiling or starting a browser. Class names prepended with an 9underscore are intended to be implementation details, and should not 10be subclassed; however, some, like FakeBrowser, have public APIs that 11may need to be called in tests. 12""" 13import types 14 15from telemetry.core import debug_data 16from telemetry.core import exceptions 17from telemetry.internal.backends.chrome_inspector import inspector_websocket 18from telemetry.internal.backends.chrome_inspector import websocket 19from telemetry.internal.browser import browser_options as browser_options_module 20from telemetry.internal.platform import system_info 21from telemetry.page import shared_page_state 22from telemetry.util import image_util 23from telemetry.util import wpr_modes 24from telemetry.testing import test_utils 25from telemetry.testing.internal import fake_gpu_info 26 27 28# Classes and functions which are intended to be part of the public 29# fakes API. 30 31class FakePlatformBackend(object): 32 def __init__(self, os_name): 33 self._os_name = os_name 34 35 def GetOSName(self): 36 return self._os_name 37 38 39class FakePlatform(object): 40 def __init__(self, os_name='', os_version_name='', arch_name=''): 41 self._network_controller = None 42 self._tracing_controller = None 43 self._arch_name = arch_name or 'FakeArchitecture' 44 self._os_name = os_name or 'FakeOS' 45 self._os_version_name = os_version_name or 'FakeVersion' 46 self._device_type_name = 'abc' 47 self._is_svelte = False 48 self._is_aosp = True 49 self._get_os_version_detail_string = 'OsVersionString' 50 self._platform_backend = FakePlatformBackend('FakeOS') 51 52 @property 53 def is_host_platform(self): 54 raise NotImplementedError 55 56 @property 57 def network_controller(self): 58 if self._network_controller is None: 59 self._network_controller = _FakeNetworkController() 60 return self._network_controller 61 62 @property 63 def tracing_controller(self): 64 if self._tracing_controller is None: 65 self._tracing_controller = _FakeTracingController() 66 return self._tracing_controller 67 68 def Initialize(self): 69 pass 70 71 def FlushDnsCache(self): 72 pass 73 74 def SetPerformanceMode(self, mode): 75 pass 76 77 def CanMonitorThermalThrottling(self): 78 return False 79 80 def IsThermallyThrottled(self): 81 return False 82 83 def HasBeenThermallyThrottled(self): 84 return False 85 86 def GetArchName(self): 87 return self._arch_name 88 89 def SetOSName(self, name): 90 self._os_name = name 91 92 def GetOSVersionName(self): 93 return self._os_version_name 94 95 def SetOSVersionName(self, os_version_name): 96 self._os_version_name = os_version_name 97 98 def GetOSName(self): 99 return self._os_name 100 101 def GetDeviceId(self): 102 return None 103 104 def GetSystemLog(self): 105 return None 106 107 def StopAllLocalServers(self): 108 pass 109 110 def WaitForBatteryTemperature(self, _): 111 pass 112 113 # TODO(rnephew): Investigate moving from setters to @property. 114 def SetDeviceTypeName(self, name): 115 self._device_type_name = name 116 117 def GetDeviceTypeName(self): 118 return self._device_type_name 119 120 def SetIsSvelte(self, b): 121 assert isinstance(b, bool) 122 self._is_svelte = b 123 124 def IsSvelte(self): 125 if self._os_name != 'android': 126 raise NotImplementedError 127 return self._is_svelte 128 129 def SetIsAosp(self, b): 130 assert isinstance(b, bool) 131 self._is_aosp = b 132 133 def IsAosp(self): 134 return self._is_aosp and self._os_name == 'android' 135 136 def SetOsVersionDetailString(self, v): 137 self._get_os_version_detail_string = v 138 139 def GetOSVersionDetailString(self): 140 return self._get_os_version_detail_string 141 142 def GetTypExpectationsTags(self): 143 return test_utils.sanitizeTypExpectationsTags( 144 [self.GetOSName(), self.GetOSVersionName()]) 145 146 147class FakeLinuxPlatform(FakePlatform): 148 def __init__(self): 149 super(FakeLinuxPlatform, self).__init__() 150 self.screenshot_png_data = None 151 self.http_server_directories = [] 152 self.http_server = FakeHTTPServer() 153 154 @property 155 def is_host_platform(self): 156 return True 157 158 def GetDeviceTypeName(self): 159 return 'Desktop' 160 161 def GetArchName(self): 162 return 'x86_64' 163 164 def GetSystemTotalPhysicalMemory(self): 165 return 8 * (1024 ** 3) 166 167 def GetOSName(self): 168 return 'linux' 169 170 def GetOSVersionName(self): 171 return 'trusty' 172 173 def GetOSVersionDetailString(self): 174 return '' 175 176 def CanTakeScreenshot(self): 177 return bool(self.screenshot_png_data) 178 179 def TakeScreenshot(self, file_path): 180 if not self.CanTakeScreenshot(): 181 raise NotImplementedError 182 img = image_util.FromBase64Png(self.screenshot_png_data) 183 image_util.WritePngFile(img, file_path) 184 return True 185 186 def SetHTTPServerDirectories(self, paths): 187 self.http_server_directories.append(paths) 188 189 190class FakeHTTPServer(object): 191 def UrlOf(self, url): 192 del url # unused 193 return 'file:///foo' 194 195 196class FakeForwarder(object): 197 def Close(self): 198 pass 199 200 201class FakeForwarderFactory(object): 202 def __init__(self): 203 self.raise_exception_on_create = False 204 self.host_ip = '127.0.0.1' 205 206 def Create(self, local_port, remote_port, reverse=False): 207 del local_port # Unused. 208 del remote_port # Unused. 209 del reverse # Unused. 210 if self.raise_exception_on_create: 211 raise exceptions.IntentionalException 212 return FakeForwarder() 213 214 215class FakePossibleBrowser(object): 216 def __init__(self, execute_on_startup=None, 217 execute_after_browser_creation=None, 218 arch_name='', os_name='', os_version_name='', browser_type=''): 219 if os_name: 220 self._returned_browser = FakeBrowser( 221 FakePlatform(os_name, os_version_name, arch_name), browser_type) 222 else: 223 self._returned_browser = FakeBrowser( 224 FakeLinuxPlatform(), browser_type) 225 self.browser_type = browser_type or 'linux' 226 self.supports_tab_control = False 227 self.execute_on_startup = execute_on_startup 228 self.execute_after_browser_creation = execute_after_browser_creation 229 self.browser_options = None # This is set in SetUpEnvironment. 230 231 @property 232 def returned_browser(self): 233 """The browser object that will be returned through later API calls.""" 234 return self._returned_browser 235 236 def Create(self): 237 if self.execute_on_startup is not None: 238 self.execute_on_startup() 239 if self.execute_after_browser_creation is not None: 240 self.execute_after_browser_creation(self._returned_browser) 241 return self.returned_browser 242 243 def SetUpEnvironment(self, browser_options): 244 self.browser_options = browser_options 245 246 def CleanUpEnvironment(self): 247 self.browser_options = None 248 249 @property 250 def platform(self): 251 """The platform object from the returned browser. 252 253 To change this or set it up, change the returned browser's 254 platform. 255 """ 256 return self.returned_browser.platform 257 258 def BrowserSession(self, options): 259 del options 260 return self.returned_browser 261 262 def GetTypExpectationsTags(self): 263 tags = self.platform.GetTypExpectationsTags() 264 return tags + test_utils.sanitizeTypExpectationsTags([self.browser_type]) 265 266 267class FakeSharedPageState(shared_page_state.SharedPageState): 268 def __init__(self, test, finder_options, story_set, possible_browser): 269 super(FakeSharedPageState, self).__init__( 270 test, finder_options, story_set, possible_browser) 271 272 def _GetPossibleBrowser(self): 273 p = FakePossibleBrowser() 274 self.ConfigurePossibleBrowser(p) 275 return p 276 277 def ConfigurePossibleBrowser(self, possible_browser): 278 """Override this to configure the PossibleBrowser. 279 280 Can make changes to the browser's configuration here via e.g.: 281 possible_browser.returned_browser.returned_system_info = ... 282 """ 283 pass 284 285 286 def DidRunStory(self, results): 287 # TODO(kbr): add a test which throws an exception from DidRunStory 288 # to verify the fix from https://crrev.com/86984d5fc56ce00e7b37ebe . 289 super(FakeSharedPageState, self).DidRunStory(results) 290 291 292class FakeSystemInfo(system_info.SystemInfo): 293 def __init__(self, model_name='', gpu_dict=None, command_line=''): 294 if gpu_dict is None: 295 gpu_dict = fake_gpu_info.FAKE_GPU_INFO 296 super(FakeSystemInfo, self).__init__(model_name, gpu_dict, command_line) 297 298 299class _FakeBrowserFinderOptions(browser_options_module.BrowserFinderOptions): 300 def __init__(self, execute_on_startup=None, 301 execute_after_browser_creation=None, *args, **kwargs): 302 browser_options_module.BrowserFinderOptions.__init__(self, *args, **kwargs) 303 self.fake_possible_browser = \ 304 FakePossibleBrowser( 305 execute_on_startup=execute_on_startup, 306 execute_after_browser_creation=execute_after_browser_creation) 307 308def CreateBrowserFinderOptions(browser_type=None, execute_on_startup=None, 309 execute_after_browser_creation=None): 310 """Creates fake browser finder options for discovering a browser.""" 311 return _FakeBrowserFinderOptions( 312 browser_type=browser_type, 313 execute_on_startup=execute_on_startup, 314 execute_after_browser_creation=execute_after_browser_creation) 315 316 317class FakeApp(object): 318 319 def __init__(self, platform=None): 320 if not platform: 321 self._platform = FakePlatform() 322 else: 323 self._platform = platform 324 self.standard_output = '' 325 self.recent_minidump_path = None 326 327 @property 328 def platform(self): 329 return self._platform 330 331 @platform.setter 332 def platform(self, incoming): 333 """Allows overriding of the fake browser's platform object.""" 334 assert isinstance(incoming, FakePlatform) 335 self._platform = incoming 336 337 def GetStandardOutput(self): 338 return self.standard_output 339 340 def GetMostRecentMinidumpPath(self): 341 return self.recent_minidump_path 342 343 def GetRecentMinidumpPathWithTimeout(self, timeout_s=15, oldest_ts=None): 344 del timeout_s, oldest_ts 345 return self.recent_minidump_path 346 347 def CollectDebugData(self, log_level): 348 del log_level 349 return debug_data.DebugData() 350 351# Internal classes. Note that end users may still need to both call 352# and mock out methods of these classes, but they should not be 353# subclassed. 354 355class FakeBrowser(FakeApp): 356 def __init__(self, platform, browser_type=''): 357 super(FakeBrowser, self).__init__(platform) 358 self._tabs = _FakeTabList(self) 359 # Fake the creation of the first tab. 360 self._tabs.New() 361 self._returned_system_info = FakeSystemInfo() 362 self._platform = platform 363 self._browser_type = browser_type or 'release' 364 self._is_crashed = False 365 366 @property 367 def returned_system_info(self): 368 """The object which will be returned from calls to GetSystemInfo.""" 369 return self._returned_system_info 370 371 @returned_system_info.setter 372 def returned_system_info(self, incoming): 373 """Allows overriding of the returned SystemInfo object. 374 375 Incoming argument must be an instance of FakeSystemInfo.""" 376 assert isinstance(incoming, FakeSystemInfo) 377 self._returned_system_info = incoming 378 379 @property 380 def browser_type(self): 381 """The browser_type this browser claims to be ('debug', 'release', etc.)""" 382 return self._browser_type 383 384 @browser_type.setter 385 def browser_type(self, incoming): 386 """Allows setting of the browser_type.""" 387 self._browser_type = incoming 388 389 def Close(self): 390 self._is_crashed = False 391 392 def GetSystemInfo(self): 393 return self.returned_system_info 394 395 @property 396 def supports_tab_control(self): 397 return True 398 399 @property 400 def tabs(self): 401 return self._tabs 402 403 @property 404 def _platform_backend(self): 405 return self._platform._platform_backend 406 407 def DumpStateUponFailure(self): 408 pass 409 410 def CleanupUnsymbolizedMinidumps(self, fatal=False): 411 del fatal 412 413 def GetTypExpectationsTags(self): 414 tags = self.platform.GetTypExpectationsTags() 415 return tags + test_utils.sanitizeTypExpectationsTags([self.browser_type]) 416 417 def __enter__(self): 418 return self 419 420 def __exit__(self, *args): 421 pass 422 423 424class _FakeTracingController(object): 425 def __init__(self): 426 self._is_tracing = False 427 428 def StartTracing(self, tracing_config, timeout=20): 429 self._is_tracing = True 430 del tracing_config 431 del timeout 432 433 def StopTracing(self): 434 self._is_tracing = False 435 436 @property 437 def is_tracing_running(self): 438 return self._is_tracing 439 440 def ClearStateIfNeeded(self): 441 pass 442 443 444class _FakeNetworkController(object): 445 def __init__(self): 446 self.wpr_mode = None 447 448 @property 449 def is_open(self): 450 return self.wpr_mode is not None 451 452 @property 453 def use_live_traffic(self): 454 return self.wpr_mode == wpr_modes.WPR_OFF 455 456 def Open(self, wpr_mode=None): 457 self.wpr_mode = wpr_mode if wpr_mode is not None else wpr_modes.WPR_REPLAY 458 459 def UpdateTrafficSettings( 460 self, round_trip_latency_ms=None, 461 download_bandwidth_kbps=None, upload_bandwidth_kbps=None): 462 pass 463 464 def Close(self): 465 self.StopReplay() 466 self.wpr_mode = None 467 468 def StartReplay(self, *args, **kwargs): 469 del args # Unused. 470 del kwargs # Unused. 471 assert self.is_open 472 473 def StopReplay(self): 474 pass 475 476 477class _FakeTab(object): 478 def __init__(self, browser, tab_id): 479 self._browser = browser 480 self._tab_id = str(tab_id) 481 self._collect_garbage_count = 0 482 self.test_png = None 483 484 @property 485 def collect_garbage_count(self): 486 return self._collect_garbage_count 487 488 @property 489 def id(self): 490 return self._tab_id 491 492 @property 493 def browser(self): 494 return self._browser 495 496 def WaitForDocumentReadyStateToBeComplete(self, timeout=0): 497 pass 498 499 def Navigate(self, url, script_to_evaluate_on_commit=None, 500 timeout=0): 501 del script_to_evaluate_on_commit, timeout # unused 502 if url == 'chrome://crash': 503 self.browser._is_crashed = True 504 raise Exception 505 506 def WaitForDocumentReadyStateToBeInteractiveOrBetter(self, timeout=0): 507 pass 508 509 def WaitForFrameToBeDisplayed(self, timeout=0): 510 pass 511 512 def IsAlive(self): 513 return True 514 515 def CloseConnections(self): 516 pass 517 518 def CollectGarbage(self): 519 self._collect_garbage_count += 1 520 521 def Close(self): 522 pass 523 524 @property 525 def screenshot_supported(self): 526 return self.test_png is not None 527 528 def Screenshot(self): 529 assert self.screenshot_supported, 'Screenshot is not supported' 530 return image_util.FromBase64Png(self.test_png) 531 532 533class _FakeTabList(object): 534 _current_tab_id = 0 535 536 def __init__(self, browser): 537 self._tabs = [] 538 self._browser = browser 539 540 def New(self, timeout=300): 541 del timeout # unused 542 type(self)._current_tab_id += 1 543 t = _FakeTab(self._browser, type(self)._current_tab_id) 544 self._tabs.append(t) 545 return t 546 547 def __iter__(self): 548 return self._tabs.__iter__() 549 550 def __len__(self): 551 return len(self._tabs) 552 553 def __getitem__(self, index): 554 if self._tabs[index].browser._is_crashed: 555 raise Exception 556 else: 557 return self._tabs[index] 558 559 def GetTabById(self, identifier): 560 """The identifier of a tab can be accessed with tab.id.""" 561 for tab in self._tabs: 562 if tab.id == identifier: 563 return tab 564 return None 565 566 567class FakeInspectorWebsocket(object): 568 _NOTIFICATION_EVENT = 1 569 _NOTIFICATION_CALLBACK = 2 570 571 """A fake InspectorWebsocket. 572 573 A fake that allows tests to send pregenerated data. Normal 574 InspectorWebsockets allow for any number of domain handlers. This fake only 575 allows up to 1 domain handler, and assumes that the domain of the response 576 always matches that of the handler. 577 """ 578 def __init__(self, mock_timer): 579 self._mock_timer = mock_timer 580 self._notifications = [] 581 self._response_handlers = {} 582 self._pending_callbacks = {} 583 self._handler = None 584 585 def RegisterDomain(self, _, handler): 586 self._handler = handler 587 588 def AddEvent(self, method, params, time): 589 if self._notifications: 590 assert self._notifications[-1][1] < time, ( 591 'Current response is scheduled earlier than previous response.') 592 response = {'method': method, 'params': params} 593 self._notifications.append((response, time, self._NOTIFICATION_EVENT)) 594 595 def AddAsyncResponse(self, method, result, time): 596 if self._notifications: 597 assert self._notifications[-1][1] < time, ( 598 'Current response is scheduled earlier than previous response.') 599 response = {'method': method, 'result': result} 600 self._notifications.append((response, time, self._NOTIFICATION_CALLBACK)) 601 602 def AddResponseHandler(self, method, handler): 603 self._response_handlers[method] = handler 604 605 def SyncRequest(self, request, *args, **kwargs): 606 del args, kwargs # unused 607 handler = self._response_handlers[request['method']] 608 return handler(request) if handler else None 609 610 def AsyncRequest(self, request, callback): 611 self._pending_callbacks.setdefault(request['method'], []).append(callback) 612 613 def SendAndIgnoreResponse(self, request): 614 pass 615 616 def Connect(self, _): 617 pass 618 619 def DispatchNotifications(self, timeout): 620 current_time = self._mock_timer.time() 621 if not self._notifications: 622 self._mock_timer.SetTime(current_time + timeout + 1) 623 raise inspector_websocket.WebSocketException( 624 websocket.WebSocketTimeoutException()) 625 626 response, time, kind = self._notifications[0] 627 if time - current_time > timeout: 628 self._mock_timer.SetTime(current_time + timeout + 1) 629 raise inspector_websocket.WebSocketException( 630 websocket.WebSocketTimeoutException()) 631 632 self._notifications.pop(0) 633 self._mock_timer.SetTime(time + 1) 634 if kind == self._NOTIFICATION_EVENT: 635 self._handler(response) 636 elif kind == self._NOTIFICATION_CALLBACK: 637 callback = self._pending_callbacks.get(response['method']).pop(0) 638 callback(response) 639 else: 640 raise Exception('Unexpected response type') 641 642 643class FakeTimer(object): 644 """ A fake timer to fake out the timing for a module. 645 Args: 646 module: module to fake out the time 647 """ 648 def __init__(self, module=None): 649 self._elapsed_time = 0 650 self._module = module 651 self._actual_time = None 652 if module: 653 assert isinstance(module, types.ModuleType) 654 self._actual_time = module.time 655 self._module.time = self 656 657 def sleep(self, time): 658 self._elapsed_time += time 659 660 def time(self): 661 return self._elapsed_time 662 663 def SetTime(self, time): 664 self._elapsed_time = time 665 666 def __del__(self): 667 self.Restore() 668 669 def Restore(self): 670 if self._module: 671 self._module.time = self._actual_time 672 self._module = None 673 self._actual_time = None 674 675 676class FakeParsedArgsForStoryFilter(object): 677 def __init__( 678 self, story_filter=None, story_filter_exclude=None, 679 story_tag_filter=None, story_tag_filter_exclude=None, 680 story_shard_begin_index=None, 681 story_shard_end_index=None, 682 run_full_story_set=None, 683 run_abridged_story_set=None, 684 run_disabled_stories=False, stories=None): 685 self.story_filter = story_filter 686 self.story_filter_exclude = story_filter_exclude 687 self.story_tag_filter = story_tag_filter 688 self.story_tag_filter_exclude = story_tag_filter_exclude 689 self.story_shard_begin_index = ( 690 story_shard_begin_index) 691 self.story_shard_end_index = ( 692 story_shard_end_index) 693 self.run_disabled_stories = run_disabled_stories 694 self.run_full_story_set = run_full_story_set 695 self.run_abridged_story_set = run_abridged_story_set 696 self.stories = stories 697