michael@0: # Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: # Use of this source code is governed by a BSD-style license that can be michael@0: # found in the LICENSE file. michael@0: michael@0: """Runs the Python tests (relies on using the Java test runner).""" michael@0: michael@0: import logging michael@0: import os michael@0: import sys michael@0: import types michael@0: michael@0: import android_commands michael@0: import apk_info michael@0: import constants michael@0: import python_test_base michael@0: from python_test_caller import CallPythonTest michael@0: from python_test_sharder import PythonTestSharder michael@0: import run_java_tests michael@0: from run_java_tests import FatalTestException michael@0: from test_info_collection import TestInfoCollection michael@0: from test_result import TestResults michael@0: michael@0: michael@0: def _GetPythonFiles(root, files): michael@0: """Returns all files from |files| that end in 'Test.py'. michael@0: michael@0: Args: michael@0: root: A directory name with python files. michael@0: files: A list of file names. michael@0: michael@0: Returns: michael@0: A list with all Python driven test file paths. michael@0: """ michael@0: return [os.path.join(root, f) for f in files if f.endswith('Test.py')] michael@0: michael@0: michael@0: def _InferImportNameFromFile(python_file): michael@0: """Given a file, infer the import name for that file. michael@0: michael@0: Example: /usr/foo/bar/baz.py -> baz. michael@0: michael@0: Args: michael@0: python_file: path to the Python file, ostensibly to import later. michael@0: michael@0: Returns: michael@0: The module name for the given file. michael@0: """ michael@0: return os.path.splitext(os.path.basename(python_file))[0] michael@0: michael@0: michael@0: def DispatchPythonTests(options): michael@0: """Dispatches the Python tests. If there are multiple devices, use sharding. michael@0: michael@0: Args: michael@0: options: command line options. michael@0: michael@0: Returns: michael@0: A list of test results. michael@0: """ michael@0: michael@0: attached_devices = android_commands.GetAttachedDevices() michael@0: if not attached_devices: michael@0: raise FatalTestException('You have no devices attached or visible!') michael@0: if options.device: michael@0: attached_devices = [options.device] michael@0: michael@0: test_collection = TestInfoCollection() michael@0: all_tests = _GetAllTests(options.python_test_root, options.official_build) michael@0: test_collection.AddTests(all_tests) michael@0: test_names = [t.qualified_name for t in all_tests] michael@0: logging.debug('All available tests: ' + str(test_names)) michael@0: michael@0: available_tests = test_collection.GetAvailableTests( michael@0: options.annotation, options.test_filter) michael@0: michael@0: if not available_tests: michael@0: logging.warning('No Python tests to run with current args.') michael@0: return TestResults() michael@0: michael@0: available_tests *= options.number_of_runs michael@0: test_names = [t.qualified_name for t in available_tests] michael@0: logging.debug('Final list of tests to run: ' + str(test_names)) michael@0: michael@0: # Copy files to each device before running any tests. michael@0: for device_id in attached_devices: michael@0: logging.debug('Pushing files to device %s', device_id) michael@0: apks = [apk_info.ApkInfo(options.test_apk_path, options.test_apk_jar_path)] michael@0: test_files_copier = run_java_tests.TestRunner(options, device_id, michael@0: None, False, 0, apks, []) michael@0: test_files_copier.CopyTestFilesOnce() michael@0: michael@0: # Actually run the tests. michael@0: if len(attached_devices) > 1 and options.wait_for_debugger: michael@0: logging.warning('Debugger can not be sharded, ' michael@0: 'using first available device') michael@0: attached_devices = attached_devices[:1] michael@0: logging.debug('Running Python tests') michael@0: sharder = PythonTestSharder(attached_devices, available_tests, options) michael@0: test_results = sharder.RunShardedTests() michael@0: michael@0: return test_results michael@0: michael@0: michael@0: def _GetTestModules(python_test_root, is_official_build): michael@0: """Retrieve a sorted list of pythonDrivenTests. michael@0: michael@0: Walks the location of pythonDrivenTests, imports them, and provides the list michael@0: of imported modules to the caller. michael@0: michael@0: Args: michael@0: python_test_root: the path to walk, looking for pythonDrivenTests michael@0: is_official_build: whether to run only those tests marked 'official' michael@0: michael@0: Returns: michael@0: A list of Python modules which may have zero or more tests. michael@0: """ michael@0: # By default run all python tests under pythonDrivenTests. michael@0: python_test_file_list = [] michael@0: for root, _, files in os.walk(python_test_root): michael@0: if (root.endswith('pythonDrivenTests') michael@0: or (is_official_build michael@0: and root.endswith('pythonDrivenTests/official'))): michael@0: python_test_file_list += _GetPythonFiles(root, files) michael@0: python_test_file_list.sort() michael@0: michael@0: test_module_list = [_GetModuleFromFile(test_file) michael@0: for test_file in python_test_file_list] michael@0: return test_module_list michael@0: michael@0: michael@0: def _GetModuleFromFile(python_file): michael@0: """Gets the module associated with a file by importing it. michael@0: michael@0: Args: michael@0: python_file: file to import michael@0: michael@0: Returns: michael@0: The module object. michael@0: """ michael@0: sys.path.append(os.path.dirname(python_file)) michael@0: import_name = _InferImportNameFromFile(python_file) michael@0: return __import__(import_name) michael@0: michael@0: michael@0: def _GetTestsFromClass(test_class): michael@0: """Create a list of test objects for each test method on this class. michael@0: michael@0: Test methods are methods on the class which begin with 'test'. michael@0: michael@0: Args: michael@0: test_class: class object which contains zero or more test methods. michael@0: michael@0: Returns: michael@0: A list of test objects, each of which is bound to one test. michael@0: """ michael@0: test_names = [m for m in dir(test_class) michael@0: if _IsTestMethod(m, test_class)] michael@0: return map(test_class, test_names) michael@0: michael@0: michael@0: def _GetTestClassesFromModule(test_module): michael@0: tests = [] michael@0: for name in dir(test_module): michael@0: attr = getattr(test_module, name) michael@0: if _IsTestClass(attr): michael@0: tests.extend(_GetTestsFromClass(attr)) michael@0: return tests michael@0: michael@0: michael@0: def _IsTestClass(test_class): michael@0: return (type(test_class) is types.TypeType and michael@0: issubclass(test_class, python_test_base.PythonTestBase) and michael@0: test_class is not python_test_base.PythonTestBase) michael@0: michael@0: michael@0: def _IsTestMethod(attrname, test_case_class): michael@0: """Checks whether this is a valid test method. michael@0: michael@0: Args: michael@0: attrname: the method name. michael@0: test_case_class: the test case class. michael@0: michael@0: Returns: michael@0: True if test_case_class.'attrname' is callable and it starts with 'test'; michael@0: False otherwise. michael@0: """ michael@0: attr = getattr(test_case_class, attrname) michael@0: return callable(attr) and attrname.startswith('test') michael@0: michael@0: michael@0: def _GetAllTests(test_root, is_official_build): michael@0: """Retrieve a list of Python test modules and their respective methods. michael@0: michael@0: Args: michael@0: test_root: path which contains Python-driven test files michael@0: is_official_build: whether this is an official build michael@0: michael@0: Returns: michael@0: List of test case objects for all available test methods. michael@0: """ michael@0: if not test_root: michael@0: return [] michael@0: all_tests = [] michael@0: test_module_list = _GetTestModules(test_root, is_official_build) michael@0: for module in test_module_list: michael@0: all_tests.extend(_GetTestClassesFromModule(module)) michael@0: return all_tests