| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 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 from collections import defaultdict |
| 6 from collections import namedtuple |
| 5 import logging | 7 import logging |
| 6 from collections import namedtuple | |
| 7 | |
| 8 from crash.stacktrace import Stacktrace | |
| 9 | 8 |
| 10 | 9 |
| 11 class CrashReport(namedtuple('CrashReport', | 10 class _FrozenDict(dict): |
| 12 ['crashed_version', 'signature', 'platform', 'stacktrace', | 11 """An immutable ``dict`` (or some approximation thereof). |
| 13 'regression_range'])): | 12 |
| 14 """A reported crash we want to analyze. | 13 The goal of this class is to render a ``dict`` hashable, so |
| 14 that it can be used as a key in other ``dict``s, so that in |
| 15 turn we can use our ``MemoizedFunction`` on functions taking |
| 16 ``CrashReportWithDependencies`` as an argument. |
| 17 |
| 18 For now, we simply define the ``__hash__`` method and assume clients |
| 19 will not try to mutate instances after they have been stored as keys |
| 20 in another ``dict``. In the future it may be worth taking further |
| 21 steps to make it more difficult for clients to do such mutation. |
| 22 |
| 23 N.B., the ``__init__`` method will clone the ``dict`` argument. There |
| 24 doesn't seem to be an easy way around this. |
| 25 """ |
| 26 def __hash__(self): |
| 27 return hash(tuple(sorted(self.items()))) |
| 28 |
| 29 |
| 30 class CrashReport(namedtuple( |
| 31 'CrashReport', ['crashed_version', 'signature', 'platform', 'stacktrace', |
| 32 'regression_range', 'dependencies', 'dependency_rolls'])): |
| 33 """A crash report with all information needed for analysis for clients. |
| 15 | 34 |
| 16 This class comprises the inputs to the Predator library; as distinguished | 35 This class comprises the inputs to the Predator library; as distinguished |
| 17 from the Culprit class, which comprises the outputs/results of Predator's | 36 from the Culprit class, which comprises the outputs/results of Predator's |
| 18 analyses. N.B., the appengine clients conflate input and output into | 37 analyses. N.B., the appengine clients conflate input and output into |
| 19 a single CrashAnalysis(ndb.Model) class, but that's up to them; in | 38 a single CrashAnalysis(ndb.Model) class, but that's up to them; in |
| 20 the library we keep inputs and outputs entirely distinct. | 39 the library we keep inputs and outputs entirely distinct. |
| 21 | 40 |
| 22 Args: | 41 Properties: |
| 23 crashed_version (str): The version of Chrome in which the crash occurred. | 42 crashed_version (str): The version of Chrome in which the crash occurred. |
| 24 signature (str): The signature of the crash on the Chrome crash server. | 43 signature (str): The signature of the crash on the Chrome crash server. |
| 25 platform (str): The platform name; e.g., 'win', 'mac', 'linux', 'android', | 44 platform (str): The platform affected by the crash; e.g., 'win', |
| 26 'ios', etc. | 45 'mac', 'linux', 'android', 'ios', etc. |
| 27 stacktrace (Stacktrace): The stacktrace of the crash. N.B., this is | 46 stacktrace (Stacktrace): The stacktrace of the crash. N.B., this is |
| 28 an object generated by parsing the string containing the stack trace; | 47 an object generated by parsing the string containing the stack trace; |
| 29 we do not store the string itself. | 48 we do not store the string itself. |
| 30 regression_range (pair or None): a pair of the last-good and first-bad | 49 regression_range (pair of str or None): a pair of ``last_good_version`` and |
| 31 versions. N.B., because this is an input, it is up to clients | 50 ``first_bad_version``. The regression_range can be ``None``. |
| 32 to call ``DetectRegressionRange`` (or whatever else) in order to | 51 dependencies (_FrozenDict): An immutable dict from dependency paths to |
| 33 provide this information. In addition, while this class does | 52 ``Dependency`` objects. The keys are all those deps which are |
| 34 support storing ``None`` to indicate a missing regression range | 53 used by both the ``crashed_version`` of the code, and at least |
| 35 (because the ClusterFuzz client wants that feature), the | 54 one frame in the ``stacktrace.crash_stack``. |
| 36 CL-classifier doesn't actually support that so you won't get a | 55 dependency_rolls (_FrozenDict) An immutable dict from dependency |
| 37 very good Culprit. The Component- and project-classifiers do still | 56 paths to ``DependencyRoll`` objects. The keys are all those |
| 38 return some results at least. | 57 dependencies which (1) occur in the regression range for the |
| 58 ``platform`` where the crash occurred, (2) neither add nor delete |
| 59 a dependency, and (3) are also keys of ``dependencies``. |
| 39 """ | 60 """ |
| 40 __slots__ = () | 61 __slots__ = () |
| 41 | 62 |
| 42 def __new__(cls, crashed_version, signature, platform, stacktrace, | 63 def __new__(cls, crashed_version, signature, platform, stacktrace, |
| 43 regression_range): | 64 regression_range, dependencies, dependency_rolls): |
| 44 assert isinstance(stacktrace, Stacktrace), TypeError( | 65 return super(CrashReport, cls).__new__( |
| 45 'In the fourth argument to CrashReport constructor, ' | 66 cls, crashed_version, signature, platform, stacktrace, |
| 46 'expected Stacktrace object, but got %s object instead.' | 67 tuple(regression_range) if regression_range else None, |
| 47 % stacktrace.__class__.__name__) | 68 _FrozenDict(dependencies) if dependencies else {}, |
| 48 | 69 _FrozenDict(dependency_rolls) if dependency_rolls else {}) |
| 49 if regression_range is None: # pragma: no cover | |
| 50 logging.warning('CrashReport.__init__: ' | |
| 51 'Got ``None`` for the regression range.') | |
| 52 else: | |
| 53 # We must ensure that the regression range is immutable, both | |
| 54 # for semantic reasons and so that it is hashable as is required | |
| 55 # for memoization. | |
| 56 regression_range = tuple(regression_range) | |
| 57 | |
| 58 return super(cls, CrashReport).__new__( | |
| 59 cls, crashed_version, signature, platform, stacktrace, regression_range) | |
| OLD | NEW |