| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """PyAuto: Python Interface to Chromium's Automation Proxy. | 6 """PyAuto: Python Interface to Chromium's Automation Proxy. |
| 7 | 7 |
| 8 PyAuto uses swig to expose Automation Proxy interfaces to Python. | 8 PyAuto uses swig to expose Automation Proxy interfaces to Python. |
| 9 For complete documentation on the functionality available, | 9 For complete documentation on the functionality available, |
| 10 run pydoc on this file. | 10 run pydoc on this file. |
| (...skipping 459 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 470 self.CloseChromeOnChromeOS() | 470 self.CloseChromeOnChromeOS() |
| 471 self.EnableChromeTestingOnChromeOS() | 471 self.EnableChromeTestingOnChromeOS() |
| 472 self.SetUp() | 472 self.SetUp() |
| 473 return | 473 return |
| 474 # Not chromeos | 474 # Not chromeos |
| 475 orig_clear_state = self.get_clear_profile() | 475 orig_clear_state = self.get_clear_profile() |
| 476 self.CloseBrowserAndServer() | 476 self.CloseBrowserAndServer() |
| 477 self.set_clear_profile(clear_profile) | 477 self.set_clear_profile(clear_profile) |
| 478 if pre_launch_hook: | 478 if pre_launch_hook: |
| 479 pre_launch_hook() | 479 pre_launch_hook() |
| 480 logging.debug('Restarting browser with clear_profile=%s' % | 480 logging.debug('Restarting browser with clear_profile=%s', |
| 481 self.get_clear_profile()) | 481 self.get_clear_profile()) |
| 482 self.LaunchBrowserAndServer() | 482 self.LaunchBrowserAndServer() |
| 483 self.set_clear_profile(orig_clear_state) # Reset to original state. | 483 self.set_clear_profile(orig_clear_state) # Reset to original state. |
| 484 | 484 |
| 485 @staticmethod | 485 @staticmethod |
| 486 def DataDir(): | 486 def DataDir(): |
| 487 """Returns the path to the data dir chrome/test/data.""" | 487 """Returns the path to the data dir chrome/test/data.""" |
| 488 return os.path.normpath( | 488 return os.path.normpath( |
| 489 os.path.join(os.path.dirname(__file__), os.pardir, "data")) | 489 os.path.join(os.path.dirname(__file__), os.pardir, "data")) |
| 490 | 490 |
| (...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 711 debug: if True, displays debug info at each retry. | 711 debug: if True, displays debug info at each retry. |
| 712 | 712 |
| 713 Returns: | 713 Returns: |
| 714 True, if returning when |function| evaluated to True | 714 True, if returning when |function| evaluated to True |
| 715 False, when returning due to timeout | 715 False, when returning due to timeout |
| 716 """ | 716 """ |
| 717 if timeout == -1: # Default | 717 if timeout == -1: # Default |
| 718 timeout = self.action_max_timeout_ms() / 1000.0 | 718 timeout = self.action_max_timeout_ms() / 1000.0 |
| 719 assert callable(function), "function should be a callable" | 719 assert callable(function), "function should be a callable" |
| 720 begin = time.time() | 720 begin = time.time() |
| 721 debug_begin = begin |
| 721 while timeout is None or time.time() - begin <= timeout: | 722 while timeout is None or time.time() - begin <= timeout: |
| 722 retval = function(*args) | 723 retval = function(*args) |
| 723 if (expect_retval is None and retval) or expect_retval == retval: | 724 if (expect_retval is None and retval) or expect_retval == retval: |
| 724 return True | 725 return True |
| 725 if debug: | 726 if debug and time.time() - debug_begin > 5: |
| 726 logging.debug('WaitUntil(%s) still waiting. ' | 727 debug_begin += 5 |
| 727 'Expecting %s. Last returned %s.' % ( | 728 if function.func_name == (lambda: True).func_name: |
| 728 function, expect_retval, retval)) | 729 function_info = inspect.getsource(function).strip() |
| 730 else: |
| 731 function_info = '%s()' % function.func_name |
| 732 logging.debug('WaitUntil(%s:%d %s) still waiting. ' |
| 733 'Expecting %s. Last returned %s.', |
| 734 os.path.basename(inspect.getsourcefile(function)), |
| 735 inspect.getsourcelines(function)[1], |
| 736 function_info, |
| 737 True if expect_retval is None else expect_retval, |
| 738 retval) |
| 729 time.sleep(retry_sleep) | 739 time.sleep(retry_sleep) |
| 730 return False | 740 return False |
| 731 | 741 |
| 732 def StartSyncServer(self): | 742 def StartSyncServer(self): |
| 733 """Start a local sync server. | 743 """Start a local sync server. |
| 734 | 744 |
| 735 Adds a dictionary attribute 'ports' in returned object. | 745 Adds a dictionary attribute 'ports' in returned object. |
| 736 | 746 |
| 737 Returns: | 747 Returns: |
| 738 A handle to Sync Server, an instance of TestServer | 748 A handle to Sync Server, an instance of TestServer |
| 739 """ | 749 """ |
| 740 sync_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_SYNC, | 750 sync_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_SYNC, |
| 741 pyautolib.FilePath('')) | 751 pyautolib.FilePath('')) |
| 742 assert sync_server.Start(), 'Could not start sync server' | 752 assert sync_server.Start(), 'Could not start sync server' |
| 743 sync_server.ports = dict(port=sync_server.GetPort(), | 753 sync_server.ports = dict(port=sync_server.GetPort(), |
| 744 xmpp_port=sync_server.GetSyncXmppPort()) | 754 xmpp_port=sync_server.GetSyncXmppPort()) |
| 745 logging.debug('Started sync server at ports %s.' % sync_server.ports) | 755 logging.debug('Started sync server at ports %s.', sync_server.ports) |
| 746 return sync_server | 756 return sync_server |
| 747 | 757 |
| 748 def StopSyncServer(self, sync_server): | 758 def StopSyncServer(self, sync_server): |
| 749 """Stop the local sync server.""" | 759 """Stop the local sync server.""" |
| 750 assert sync_server, 'Sync Server not yet started' | 760 assert sync_server, 'Sync Server not yet started' |
| 751 assert sync_server.Stop(), 'Could not stop sync server' | 761 assert sync_server.Stop(), 'Could not stop sync server' |
| 752 logging.debug('Stopped sync server at ports %s.' % sync_server.ports) | 762 logging.debug('Stopped sync server at ports %s.', sync_server.ports) |
| 753 | 763 |
| 754 def StartFTPServer(self, data_dir): | 764 def StartFTPServer(self, data_dir): |
| 755 """Start a local file server hosting data files over ftp:// | 765 """Start a local file server hosting data files over ftp:// |
| 756 | 766 |
| 757 Args: | 767 Args: |
| 758 data_dir: path where ftp files should be served | 768 data_dir: path where ftp files should be served |
| 759 | 769 |
| 760 Returns: | 770 Returns: |
| 761 handle to FTP Server, an instance of TestServer | 771 handle to FTP Server, an instance of TestServer |
| 762 """ | 772 """ |
| 763 ftp_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_FTP, | 773 ftp_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_FTP, |
| 764 pyautolib.FilePath(data_dir)) | 774 pyautolib.FilePath(data_dir)) |
| 765 assert ftp_server.Start(), 'Could not start ftp server' | 775 assert ftp_server.Start(), 'Could not start ftp server' |
| 766 logging.debug('Started ftp server at "%s".' % data_dir) | 776 logging.debug('Started ftp server at "%s".', data_dir) |
| 767 return ftp_server | 777 return ftp_server |
| 768 | 778 |
| 769 def StopFTPServer(self, ftp_server): | 779 def StopFTPServer(self, ftp_server): |
| 770 """Stop the local ftp server.""" | 780 """Stop the local ftp server.""" |
| 771 assert ftp_server, 'FTP Server not yet started' | 781 assert ftp_server, 'FTP Server not yet started' |
| 772 assert ftp_server.Stop(), 'Could not stop ftp server' | 782 assert ftp_server.Stop(), 'Could not stop ftp server' |
| 773 logging.debug('Stopped ftp server.') | 783 logging.debug('Stopped ftp server.') |
| 774 | 784 |
| 775 def StartHTTPServer(self, data_dir): | 785 def StartHTTPServer(self, data_dir): |
| 776 """Starts a local HTTP TestServer serving files from |data_dir|. | 786 """Starts a local HTTP TestServer serving files from |data_dir|. |
| 777 | 787 |
| 778 Args: | 788 Args: |
| 779 data_dir: path where the TestServer should serve files from. This will be | 789 data_dir: path where the TestServer should serve files from. This will be |
| 780 appended to the source dir to get the final document root. | 790 appended to the source dir to get the final document root. |
| 781 | 791 |
| 782 Returns: | 792 Returns: |
| 783 handle to the HTTP TestServer | 793 handle to the HTTP TestServer |
| 784 """ | 794 """ |
| 785 http_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_HTTP, | 795 http_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_HTTP, |
| 786 pyautolib.FilePath(data_dir)) | 796 pyautolib.FilePath(data_dir)) |
| 787 assert http_server.Start(), 'Could not start HTTP server' | 797 assert http_server.Start(), 'Could not start HTTP server' |
| 788 logging.debug('Started HTTP server at "%s".' % data_dir) | 798 logging.debug('Started HTTP server at "%s".', data_dir) |
| 789 return http_server | 799 return http_server |
| 790 | 800 |
| 791 def StopHTTPServer(self, http_server): | 801 def StopHTTPServer(self, http_server): |
| 792 assert http_server, 'HTTP server not yet started' | 802 assert http_server, 'HTTP server not yet started' |
| 793 assert http_server.Stop(), 'Cloud not stop the HTTP server' | 803 assert http_server.Stop(), 'Cloud not stop the HTTP server' |
| 794 logging.debug('Stopped HTTP server.') | 804 logging.debug('Stopped HTTP server.') |
| 795 | 805 |
| 796 class ActionTimeoutChanger(object): | 806 class ActionTimeoutChanger(object): |
| 797 """Facilitate temporary changes to action_timeout_ms. | 807 """Facilitate temporary changes to action_timeout_ms. |
| 798 | 808 |
| (...skipping 3717 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4516 | 4526 |
| 4517 def _StartHTTPServer(self): | 4527 def _StartHTTPServer(self): |
| 4518 """Start a local file server hosting data files over http://""" | 4528 """Start a local file server hosting data files over http://""" |
| 4519 global _HTTP_SERVER | 4529 global _HTTP_SERVER |
| 4520 assert not _HTTP_SERVER, 'HTTP Server already started' | 4530 assert not _HTTP_SERVER, 'HTTP Server already started' |
| 4521 http_data_dir = _OPTIONS.http_data_dir | 4531 http_data_dir = _OPTIONS.http_data_dir |
| 4522 http_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_HTTP, | 4532 http_server = pyautolib.TestServer(pyautolib.TestServer.TYPE_HTTP, |
| 4523 pyautolib.FilePath(http_data_dir)) | 4533 pyautolib.FilePath(http_data_dir)) |
| 4524 assert http_server.Start(), 'Could not start http server' | 4534 assert http_server.Start(), 'Could not start http server' |
| 4525 _HTTP_SERVER = http_server | 4535 _HTTP_SERVER = http_server |
| 4526 logging.debug('Started http server at "%s".' % http_data_dir) | 4536 logging.debug('Started http server at "%s".', http_data_dir) |
| 4527 | 4537 |
| 4528 def _StopHTTPServer(self): | 4538 def _StopHTTPServer(self): |
| 4529 """Stop the local http server.""" | 4539 """Stop the local http server.""" |
| 4530 global _HTTP_SERVER | 4540 global _HTTP_SERVER |
| 4531 assert _HTTP_SERVER, 'HTTP Server not yet started' | 4541 assert _HTTP_SERVER, 'HTTP Server not yet started' |
| 4532 assert _HTTP_SERVER.Stop(), 'Could not stop http server' | 4542 assert _HTTP_SERVER.Stop(), 'Could not stop http server' |
| 4533 _HTTP_SERVER = None | 4543 _HTTP_SERVER = None |
| 4534 logging.debug('Stopped http server.') | 4544 logging.debug('Stopped http server.') |
| 4535 | 4545 |
| 4536 def _ConnectToRemoteHosts(self, addresses): | 4546 def _ConnectToRemoteHosts(self, addresses): |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4755 obj = getattr(obj, part) | 4765 obj = getattr(obj, part) |
| 4756 | 4766 |
| 4757 if type(obj) == types.ModuleType: | 4767 if type(obj) == types.ModuleType: |
| 4758 return _GetTestsFromModule(obj) | 4768 return _GetTestsFromModule(obj) |
| 4759 elif (isinstance(obj, (type, types.ClassType)) and | 4769 elif (isinstance(obj, (type, types.ClassType)) and |
| 4760 issubclass(obj, PyUITest) and obj != PyUITest): | 4770 issubclass(obj, PyUITest) and obj != PyUITest): |
| 4761 return [module.__name__ + '.' + x for x in _GetTestsFromTestCase(obj)] | 4771 return [module.__name__ + '.' + x for x in _GetTestsFromTestCase(obj)] |
| 4762 elif type(obj) == types.UnboundMethodType: | 4772 elif type(obj) == types.UnboundMethodType: |
| 4763 return [name] | 4773 return [name] |
| 4764 else: | 4774 else: |
| 4765 logging.warn('No tests in "%s"' % name) | 4775 logging.warn('No tests in "%s"', name) |
| 4766 return [] | 4776 return [] |
| 4767 | 4777 |
| 4768 def _ListMissingTests(self): | 4778 def _ListMissingTests(self): |
| 4769 """Print tests missing from PYAUTO_TESTS.""" | 4779 """Print tests missing from PYAUTO_TESTS.""" |
| 4770 # Fetch tests from all test scripts | 4780 # Fetch tests from all test scripts |
| 4771 all_test_files = filter(lambda x: x.endswith('.py'), | 4781 all_test_files = filter(lambda x: x.endswith('.py'), |
| 4772 os.listdir(self.TestsDir())) | 4782 os.listdir(self.TestsDir())) |
| 4773 all_tests_modules = [os.path.splitext(x)[0] for x in all_test_files] | 4783 all_tests_modules = [os.path.splitext(x)[0] for x in all_test_files] |
| 4774 all_tests = reduce(lambda x, y: x + y, | 4784 all_tests = reduce(lambda x, y: x + y, |
| 4775 map(self._ImportTestsFromName, all_tests_modules)) | 4785 map(self._ImportTestsFromName, all_tests_modules)) |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 4816 ] | 4826 ] |
| 4817 """ | 4827 """ |
| 4818 if not args: # Load tests ourselves | 4828 if not args: # Load tests ourselves |
| 4819 if self._HasTestCases('__main__'): # we are running a test script | 4829 if self._HasTestCases('__main__'): # we are running a test script |
| 4820 module_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] | 4830 module_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] |
| 4821 args.append(module_name) # run the test cases found in it | 4831 args.append(module_name) # run the test cases found in it |
| 4822 else: # run tests from the test description file | 4832 else: # run tests from the test description file |
| 4823 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename) | 4833 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename) |
| 4824 logging.debug("Reading %s", pyauto_tests_file) | 4834 logging.debug("Reading %s", pyauto_tests_file) |
| 4825 if not os.path.exists(pyauto_tests_file): | 4835 if not os.path.exists(pyauto_tests_file): |
| 4826 logging.warn("%s missing. Cannot load tests." % pyauto_tests_file) | 4836 logging.warn("%s missing. Cannot load tests.", pyauto_tests_file) |
| 4827 else: | 4837 else: |
| 4828 args = self._ExpandTestNamesFrom(pyauto_tests_file, | 4838 args = self._ExpandTestNamesFrom(pyauto_tests_file, |
| 4829 self._options.suite) | 4839 self._options.suite) |
| 4830 return args | 4840 return args |
| 4831 | 4841 |
| 4832 def _ExpandTestNamesFrom(self, filename, suite): | 4842 def _ExpandTestNamesFrom(self, filename, suite): |
| 4833 """Load test names from the given file. | 4843 """Load test names from the given file. |
| 4834 | 4844 |
| 4835 Args: | 4845 Args: |
| 4836 filename: the file to read the tests from | 4846 filename: the file to read the tests from |
| 4837 suite: the name of the suite to load from |filename|. | 4847 suite: the name of the suite to load from |filename|. |
| 4838 | 4848 |
| 4839 Returns: | 4849 Returns: |
| 4840 a list of test names | 4850 a list of test names |
| 4841 [module.testcase.testX, module.testcase.testY, ..] | 4851 [module.testcase.testX, module.testcase.testY, ..] |
| 4842 """ | 4852 """ |
| 4843 suites = PyUITest.EvalDataFrom(filename) | 4853 suites = PyUITest.EvalDataFrom(filename) |
| 4844 platform = sys.platform | 4854 platform = sys.platform |
| 4845 if PyUITest.IsChromeOS(): # check if it's chromeos | 4855 if PyUITest.IsChromeOS(): # check if it's chromeos |
| 4846 platform = 'chromeos' | 4856 platform = 'chromeos' |
| 4847 assert platform in self._platform_map, '%s unsupported' % platform | 4857 assert platform in self._platform_map, '%s unsupported' % platform |
| 4848 def _NamesInSuite(suite_name): | 4858 def _NamesInSuite(suite_name): |
| 4849 logging.debug('Expanding suite %s' % suite_name) | 4859 logging.debug('Expanding suite %s', suite_name) |
| 4850 platforms = suites.get(suite_name) | 4860 platforms = suites.get(suite_name) |
| 4851 names = platforms.get('all', []) + \ | 4861 names = platforms.get('all', []) + \ |
| 4852 platforms.get(self._platform_map[platform], []) | 4862 platforms.get(self._platform_map[platform], []) |
| 4853 ret = [] | 4863 ret = [] |
| 4854 # Recursively include suites if any. Suites begin with @. | 4864 # Recursively include suites if any. Suites begin with @. |
| 4855 for name in names: | 4865 for name in names: |
| 4856 if name.startswith('@'): # Include another suite | 4866 if name.startswith('@'): # Include another suite |
| 4857 ret.extend(_NamesInSuite(name[1:])) | 4867 ret.extend(_NamesInSuite(name[1:])) |
| 4858 else: | 4868 else: |
| 4859 ret.append(name) | 4869 ret.append(name) |
| 4860 return ret | 4870 return ret |
| 4861 | 4871 |
| 4862 assert suite in suites, '%s: No such suite in %s' % (suite, filename) | 4872 assert suite in suites, '%s: No such suite in %s' % (suite, filename) |
| 4863 all_names = _NamesInSuite(suite) | 4873 all_names = _NamesInSuite(suite) |
| 4864 args = [] | 4874 args = [] |
| 4865 excluded = [] | 4875 excluded = [] |
| 4866 # Find all excluded tests. Excluded tests begin with '-'. | 4876 # Find all excluded tests. Excluded tests begin with '-'. |
| 4867 for name in all_names: | 4877 for name in all_names: |
| 4868 if name.startswith('-'): # Exclude | 4878 if name.startswith('-'): # Exclude |
| 4869 excluded.extend(self._ImportTestsFromName(name[1:])) | 4879 excluded.extend(self._ImportTestsFromName(name[1:])) |
| 4870 else: | 4880 else: |
| 4871 args.extend(self._ImportTestsFromName(name)) | 4881 args.extend(self._ImportTestsFromName(name)) |
| 4872 for name in excluded: | 4882 for name in excluded: |
| 4873 if name in args: | 4883 if name in args: |
| 4874 args.remove(name) | 4884 args.remove(name) |
| 4875 else: | 4885 else: |
| 4876 logging.warn('Cannot exclude %s. Not included. Ignoring' % name) | 4886 logging.warn('Cannot exclude %s. Not included. Ignoring', name) |
| 4877 if excluded: | 4887 if excluded: |
| 4878 logging.debug('Excluded %d test(s): %s' % (len(excluded), excluded)) | 4888 logging.debug('Excluded %d test(s): %s', len(excluded), excluded) |
| 4879 return args | 4889 return args |
| 4880 | 4890 |
| 4881 def _Run(self): | 4891 def _Run(self): |
| 4882 """Run the tests.""" | 4892 """Run the tests.""" |
| 4883 if self._options.wait_for_debugger: | 4893 if self._options.wait_for_debugger: |
| 4884 raw_input('Attach debugger to process %s and hit <enter> ' % os.getpid()) | 4894 raw_input('Attach debugger to process %s and hit <enter> ' % os.getpid()) |
| 4885 | 4895 |
| 4886 suite_args = [sys.argv[0]] | 4896 suite_args = [sys.argv[0]] |
| 4887 chrome_flags = self._options.chrome_flags | 4897 chrome_flags = self._options.chrome_flags |
| 4888 # Set CHROME_HEADLESS. It enables crash reporter on posix. | 4898 # Set CHROME_HEADLESS. It enables crash reporter on posix. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 4907 successful = result.wasSuccessful() | 4917 successful = result.wasSuccessful() |
| 4908 if not successful: | 4918 if not successful: |
| 4909 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename) | 4919 pyauto_tests_file = os.path.join(self.TestsDir(), self._tests_filename) |
| 4910 print >>sys.stderr, 'Tests can be disabled by editing %s. ' \ | 4920 print >>sys.stderr, 'Tests can be disabled by editing %s. ' \ |
| 4911 'Ref: %s' % (pyauto_tests_file, _PYAUTO_DOC_URL) | 4921 'Ref: %s' % (pyauto_tests_file, _PYAUTO_DOC_URL) |
| 4912 sys.exit(not successful) | 4922 sys.exit(not successful) |
| 4913 | 4923 |
| 4914 | 4924 |
| 4915 if __name__ == '__main__': | 4925 if __name__ == '__main__': |
| 4916 Main() | 4926 Main() |
| OLD | NEW |