| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 import os | 6 import os |
| 7 import pickle | 7 import pickle |
| 8 import re | 8 import re |
| 9 import sys | 9 import sys |
| 10 | 10 |
| 11 from pylib import cmd_helper | 11 from pylib import cmd_helper |
| 12 from pylib import constants | 12 from pylib import constants |
| 13 from pylib import flag_changer | 13 from pylib import flag_changer |
| 14 from pylib.base import base_test_result | 14 from pylib.base import base_test_result |
| 15 from pylib.base import test_instance | 15 from pylib.base import test_instance |
| 16 from pylib.instrumentation import test_result | 16 from pylib.instrumentation import test_result |
| 17 from pylib.instrumentation import instrumentation_parser | 17 from pylib.instrumentation import instrumentation_parser |
| 18 from pylib.utils import apk_helper | 18 from pylib.utils import apk_helper |
| 19 from pylib.utils import md5sum | 19 from pylib.utils import md5sum |
| 20 from pylib.utils import proguard | 20 from pylib.utils import proguard |
| 21 | 21 |
| 22 sys.path.append( | 22 sys.path.append( |
| 23 os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common')) | 23 os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common')) |
| 24 import unittest_util | 24 import unittest_util |
| 25 | 25 |
| 26 # Ref: http://developer.android.com/reference/android/app/Activity.html | |
| 27 _ACTIVITY_RESULT_CANCELED = 0 | |
| 28 _ACTIVITY_RESULT_OK = -1 | |
| 29 | |
| 30 _DEFAULT_ANNOTATIONS = [ | 26 _DEFAULT_ANNOTATIONS = [ |
| 31 'Smoke', 'SmallTest', 'MediumTest', 'LargeTest', | 27 'Smoke', 'SmallTest', 'MediumTest', 'LargeTest', |
| 32 'EnormousTest', 'IntegrationTest'] | 28 'EnormousTest', 'IntegrationTest'] |
| 33 _NATIVE_CRASH_RE = re.compile('native crash', re.IGNORECASE) | 29 _NATIVE_CRASH_RE = re.compile('native crash', re.IGNORECASE) |
| 34 _PICKLE_FORMAT_VERSION = 10 | 30 _PICKLE_FORMAT_VERSION = 10 |
| 35 | 31 |
| 36 | 32 |
| 37 # TODO(jbudorick): Make these private class methods of | 33 # TODO(jbudorick): Make these private class methods of |
| 38 # InstrumentationTestInstance once the instrumentation test_runner is | 34 # InstrumentationTestInstance once the instrumentation test_runner is |
| 39 # deprecated. | 35 # deprecated. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 51 - the status code as an integer | 47 - the status code as an integer |
| 52 - the bundle dump as a dict mapping string keys to a list of | 48 - the bundle dump as a dict mapping string keys to a list of |
| 53 strings, one for each line. | 49 strings, one for each line. |
| 54 """ | 50 """ |
| 55 parser = instrumentation_parser.InstrumentationParser(raw_output) | 51 parser = instrumentation_parser.InstrumentationParser(raw_output) |
| 56 statuses = list(parser.IterStatus()) | 52 statuses = list(parser.IterStatus()) |
| 57 code, bundle = parser.GetResult() | 53 code, bundle = parser.GetResult() |
| 58 return (code, bundle, statuses) | 54 return (code, bundle, statuses) |
| 59 | 55 |
| 60 | 56 |
| 61 def GenerateTestResults( | 57 def GenerateTestResult(test_name, instr_statuses, start_ms, duration_ms): |
| 62 result_code, result_bundle, statuses, start_ms, duration_ms): | 58 """Generate the result of |test| from |instr_statuses|. |
| 63 """Generate test results from |statuses|. | |
| 64 | 59 |
| 65 Args: | 60 Args: |
| 66 result_code: The overall status code as an integer. | 61 test_name: The name of the test as "class#method" |
| 67 result_bundle: The summary bundle dump as a dict. | 62 instr_statuses: A list of 2-tuples containing: |
| 68 statuses: A list of 2-tuples containing: | |
| 69 - the status code as an integer | 63 - the status code as an integer |
| 70 - the bundle dump as a dict mapping string keys to string values | 64 - the bundle dump as a dict mapping string keys to string values |
| 71 Note that this is the same as the third item in the 3-tuple returned by | 65 Note that this is the same as the third item in the 3-tuple returned by |
| 72 |_ParseAmInstrumentRawOutput|. | 66 |_ParseAmInstrumentRawOutput|. |
| 73 start_ms: The start time of the test in milliseconds. | 67 start_ms: The start time of the test in milliseconds. |
| 74 duration_ms: The duration of the test in milliseconds. | 68 duration_ms: The duration of the test in milliseconds. |
| 69 Returns: |
| 70 An InstrumentationTestResult object. |
| 71 """ |
| 72 log = '' |
| 73 result_type = base_test_result.ResultType.UNKNOWN |
| 75 | 74 |
| 76 Returns: | 75 for status_code, bundle in instr_statuses: |
| 77 A list containing an instance of InstrumentationTestResult for each test | 76 if status_code == instrumentation_parser.STATUS_CODE_START: |
| 78 parsed. | 77 pass |
| 79 """ | 78 elif status_code == instrumentation_parser.STATUS_CODE_OK: |
| 79 bundle_test = '%s#%s' % (bundle.get('class', ''), bundle.get('test', '')) |
| 80 skipped = bundle.get('test_skipped', '') |
| 80 | 81 |
| 81 results = [] | 82 if (test_name == bundle_test and |
| 83 result_type == base_test_result.ResultType.UNKNOWN): |
| 84 result_type = base_test_result.ResultType.PASS |
| 85 elif skipped.lower() in ('true', '1', 'yes'): |
| 86 result_type = base_test_result.ResultType.SKIP |
| 87 logging.info('Skipped ' + test_name) |
| 88 else: |
| 89 if status_code not in (instrumentation_parser.STATUS_CODE_ERROR, |
| 90 instrumentation_parser.STATUS_CODE_FAILURE): |
| 91 logging.error('Unrecognized status code %d. Handling as an error.', |
| 92 status_code) |
| 93 result_type = base_test_result.ResultType.FAIL |
| 94 if 'stack' in bundle: |
| 95 log = bundle['stack'] |
| 82 | 96 |
| 83 current_result = None | 97 return test_result.InstrumentationTestResult( |
| 84 | 98 test_name, result_type, start_ms, duration_ms, log=log) |
| 85 for status_code, bundle in statuses: | |
| 86 test_class = bundle.get('class', '') | |
| 87 test_method = bundle.get('test', '') | |
| 88 if test_class and test_method: | |
| 89 test_name = '%s#%s' % (test_class, test_method) | |
| 90 else: | |
| 91 continue | |
| 92 | |
| 93 if status_code == instrumentation_parser.STATUS_CODE_START: | |
| 94 if current_result: | |
| 95 results.append(current_result) | |
| 96 current_result = test_result.InstrumentationTestResult( | |
| 97 test_name, base_test_result.ResultType.UNKNOWN, start_ms, duration_ms) | |
| 98 else: | |
| 99 if status_code == instrumentation_parser.STATUS_CODE_OK: | |
| 100 if bundle.get('test_skipped', '').lower() in ('true', '1', 'yes'): | |
| 101 current_result.SetType(base_test_result.ResultType.SKIP) | |
| 102 elif current_result.GetType() == base_test_result.ResultType.UNKNOWN: | |
| 103 current_result.SetType(base_test_result.ResultType.PASS) | |
| 104 else: | |
| 105 if status_code not in (instrumentation_parser.STATUS_CODE_ERROR, | |
| 106 instrumentation_parser.STATUS_CODE_FAILURE): | |
| 107 logging.error('Unrecognized status code %d. Handling as an error.', | |
| 108 status_code) | |
| 109 current_result.SetType(base_test_result.ResultType.FAIL) | |
| 110 if 'stack' in bundle: | |
| 111 current_result.SetLog(bundle['stack']) | |
| 112 | |
| 113 if current_result: | |
| 114 if current_result.GetType() == base_test_result.ResultType.UNKNOWN: | |
| 115 crashed = (result_code == _ACTIVITY_RESULT_CANCELED | |
| 116 and any(_NATIVE_CRASH_RE.search(l) | |
| 117 for l in result_bundle.itervalues())) | |
| 118 if crashed: | |
| 119 current_result.SetType(base_test_result.ResultType.CRASH) | |
| 120 | |
| 121 results.append(current_result) | |
| 122 | |
| 123 return results | |
| 124 | 99 |
| 125 | 100 |
| 126 class InstrumentationTestInstance(test_instance.TestInstance): | 101 class InstrumentationTestInstance(test_instance.TestInstance): |
| 127 | 102 |
| 128 def __init__(self, args, isolate_delegate, error_func): | 103 def __init__(self, args, isolate_delegate, error_func): |
| 129 super(InstrumentationTestInstance, self).__init__() | 104 super(InstrumentationTestInstance, self).__init__() |
| 130 | 105 |
| 131 self._apk_under_test = None | 106 self._apk_under_test = None |
| 132 self._package_info = None | 107 self._package_info = None |
| 133 self._test_apk = None | 108 self._test_apk = None |
| (...skipping 305 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 439 a = dict(c['annotations']) | 414 a = dict(c['annotations']) |
| 440 a.update(m['annotations']) | 415 a.update(m['annotations']) |
| 441 inflated_tests.append({ | 416 inflated_tests.append({ |
| 442 'class': c['class'], | 417 'class': c['class'], |
| 443 'method': m['method'], | 418 'method': m['method'], |
| 444 'annotations': a, | 419 'annotations': a, |
| 445 }) | 420 }) |
| 446 return inflated_tests | 421 return inflated_tests |
| 447 | 422 |
| 448 @staticmethod | 423 @staticmethod |
| 424 def GenerateMultiTestResult(errors, statuses): |
| 425 results = [] |
| 426 skip_counter = 1 |
| 427 for status_code, bundle in statuses: |
| 428 if status_code != instrumentation_parser.STATUS_CODE_START: |
| 429 # TODO(rnephew): Make skipped tests still output test name. This is only |
| 430 # there to give skipped tests a unique name so they are counted |
| 431 if 'test_skipped' in bundle: |
| 432 test_name = str(skip_counter) |
| 433 skip_counter += 1 |
| 434 else: |
| 435 test_name = '%s#%s' % (bundle.get('class', ''), |
| 436 bundle.get('test', '')) |
| 437 |
| 438 results.append( |
| 439 GenerateTestResult(test_name, [(status_code, bundle)], 0, 0)) |
| 440 for error in errors.itervalues(): |
| 441 if _NATIVE_CRASH_RE.search(error): |
| 442 results.append( |
| 443 base_test_result.BaseTestResult( |
| 444 'Crash detected', base_test_result.ResultType.CRASH)) |
| 445 |
| 446 return results |
| 447 |
| 448 @staticmethod |
| 449 def ParseAmInstrumentRawOutput(raw_output): | 449 def ParseAmInstrumentRawOutput(raw_output): |
| 450 return ParseAmInstrumentRawOutput(raw_output) | 450 return ParseAmInstrumentRawOutput(raw_output) |
| 451 | 451 |
| 452 @staticmethod | 452 @staticmethod |
| 453 def GenerateTestResults( | 453 def GenerateTestResult(test_name, instr_statuses, start_ms, duration_ms): |
| 454 result_code, result_bundle, statuses, start_ms, duration_ms): | 454 return GenerateTestResult(test_name, instr_statuses, start_ms, duration_ms) |
| 455 return GenerateTestResults(result_code, result_bundle, statuses, | |
| 456 start_ms, duration_ms) | |
| 457 | 455 |
| 458 #override | 456 #override |
| 459 def TearDown(self): | 457 def TearDown(self): |
| 460 if self._isolate_delegate: | 458 if self._isolate_delegate: |
| 461 self._isolate_delegate.Clear() | 459 self._isolate_delegate.Clear() |
| 462 | 460 |
| OLD | NEW |