| Index: appengine/findit/crash/test/findit_for_chromecrash_test.py
|
| diff --git a/appengine/findit/crash/test/findit_for_chromecrash_test.py b/appengine/findit/crash/test/findit_for_chromecrash_test.py
|
| index c0d26914683040ddea608807659eaea0192ec188..27b6711c74c21e4c80842b4273f558853074ba96 100644
|
| --- a/appengine/findit/crash/test/findit_for_chromecrash_test.py
|
| +++ b/appengine/findit/crash/test/findit_for_chromecrash_test.py
|
| @@ -6,16 +6,72 @@ from common import chrome_dependency_fetcher
|
| from common import git_repository
|
| from common.dependency import DependencyRoll
|
| from common.http_client_appengine import HttpClientAppengine
|
| +from crash import chromecrash_parser
|
| from crash import detect_regression_range
|
| from crash import findit_for_chromecrash
|
| -from crash import chromecrash_parser
|
| -from crash import findit_for_crash
|
| +from crash.changelist_classifier import ChangelistClassifier
|
| +from crash.chromecrash_parser import ChromeCrashParser
|
| from crash.component_classifier import ComponentClassifier
|
| +from crash.crash_report import CrashReport
|
| +from crash.culprit import Culprit
|
| +from crash.culprit import NullCulprit
|
| +from crash.findit_for_chromecrash import FinditForChromeCrash
|
| +from crash.findit_for_chromecrash import FinditForFracas
|
| +from crash.findit import Findit
|
| from crash.project_classifier import ProjectClassifier
|
| from crash.results import MatchResult
|
| from crash.stacktrace import CallStack
|
| from crash.stacktrace import Stacktrace
|
| +from crash.test.crash_pipeline_test import DummyCrashData
|
| from crash.test.crash_testcase import CrashTestCase
|
| +from crash.type_enums import CrashClient
|
| +from model import analysis_status
|
| +from model.crash.crash_analysis import CrashAnalysis
|
| +from model.crash.fracas_crash_analysis import FracasCrashAnalysis
|
| +
|
| +# In production we'd use CrashWrapperPipeline. And that'd work fine here,
|
| +# since we never actually call the method that uses it. But just to be
|
| +# absolutely sure we don't go over the wire due to some mocking failure,
|
| +# we'll use this dummy class instead. (In fact, since it's never used,
|
| +# we don't even need to give a real class; |None| works just fine.)
|
| +MOCK_PIPELINE_CLS = None
|
| +
|
| +MOCK_REPOSITORY = None
|
| +
|
| +class _FinditForChromeCrash(FinditForChromeCrash):
|
| + # We allow overriding the default MOCK_REPOSITORY because one unittest
|
| + # needs to.
|
| + def __init__(self, repository=MOCK_REPOSITORY):
|
| + super(_FinditForChromeCrash, self).__init__(repository, MOCK_PIPELINE_CLS)
|
| +
|
| + @classmethod
|
| + def _ClientID(cls): # pragma: no cover
|
| + """Avoid throwing a NotImplementedError.
|
| +
|
| + Since this method is called from |FinditForChromeCrash.__init__|
|
| + in order to construct the Azalea object, we need to not throw
|
| + exceptions since we want to be able to test the FinditForChromeCrash
|
| + class itself.
|
| + """
|
| + return ''
|
| +
|
| + @property
|
| + def config(self):
|
| + """Avoid returning None.
|
| +
|
| + The default |Findit.config| will return None if the client
|
| + id is not found in the CrashConfig. This in turn will cause
|
| + |FinditForChromeCrash.__init__| to crash, since NoneType doesn't
|
| + have a |get| method. In general it's fine for things to crash, since
|
| + noone should make instances of Findit subclasses which don't define
|
| + |_clientID|; but for this test suite, we want to permit instances
|
| + of FinditForChromeCrash, so that we can test that class directly.
|
| + """
|
| + return {}
|
| +
|
| +def _FinditForFracas():
|
| + """A helper to pass in the standard pipeline class."""
|
| + return FinditForFracas(MOCK_REPOSITORY, MOCK_PIPELINE_CLS)
|
|
|
|
|
| class FinditForChromeCrashTest(CrashTestCase):
|
| @@ -23,88 +79,206 @@ class FinditForChromeCrashTest(CrashTestCase):
|
| chrome_dep_fetcher = chrome_dependency_fetcher.ChromeDependencyFetcher(
|
| git_repository.GitRepository(http_client=HttpClientAppengine()))
|
|
|
| - def testFindCulpritForChromeCrashEmptyStacktrace(self):
|
| - def _MockGetDependency(*_):
|
| - return {}
|
| + # TODO(wrengr): what was the purpose of this test? As written it's
|
| + # just testing that mocking works. I'm guessing it was to check that
|
| + # we fail when the analysis is for the wrong client_id; but if so,
|
| + # then we shouldn't need to mock FindCulprit...
|
| + def testFindCulprit(self):
|
| + self.mock(FinditForChromeCrash, 'FindCulprit',
|
| + lambda self, *_: NullCulprit())
|
|
|
| - def _MockParse(*_):
|
| - return Stacktrace()
|
| + # TODO(wrengr): would be less fragile to call
|
| + # FinditForFracas.CreateAnalysis instead; though if I'm right about
|
| + # the original purpose of this test, then this is one of the few
|
| + # places where calling FracasCrashAnalysis directly would actually
|
| + # make sense.
|
| + analysis = FracasCrashAnalysis.Create({'signature': 'sig'})
|
| + # TODO(wrengr): shouldn't FracasCrashAnalysis.Create already have set
|
| + # the client_id?
|
| + analysis.client_id = CrashClient.FRACAS
|
|
|
| - findit_client = findit_for_chromecrash.FinditForChromeCrash(
|
| + findit_client = _FinditForChromeCrash(
|
| git_repository.GitRepository(http_client=HttpClientAppengine()))
|
| - self.mock(findit_client.dep_fetcher, 'GetDependency',
|
| - _MockGetDependency)
|
| - self.mock(chromecrash_parser.ChromeCrashParser, 'Parse', _MockParse)
|
| + result, tags = findit_client.FindCulprit(analysis).ToDicts()
|
| + # TODO(wrengr): just test for the NullCulprit directly; instead of
|
| + # going through |ToDicts|.
|
| + expected_result, expected_tags = NullCulprit().ToDicts()
|
| + self.assertDictEqual(result, expected_result)
|
| + self.assertDictEqual(tags, expected_tags)
|
|
|
| - expected_results = {'found': False}
|
| - expected_tag = {'found_suspects': False,
|
| - 'has_regression_range': False}
|
|
|
| - results, tag = findit_client.FindCulprit(
|
| - 'signature', 'win', 'frame1\nframe2', '50.0.1234.0',
|
| - [{'chrome_version': '50.0.1234.0', 'cpm': 0.6}]).ToDicts()
|
| +class FinditForFracasTest(CrashTestCase):
|
|
|
| - self.assertEqual(expected_results, results)
|
| - self.assertEqual(expected_tag, tag)
|
| + def testPlatformRename(self):
|
| + self.assertEqual(_FinditForFracas().RenamePlatform('linux'), 'unix')
|
|
|
| - def testFindCulpritForChromeCrash(self):
|
| - def _MockGetDependency(*_):
|
| - return {}
|
| + def testCheckPolicyUnsupportedPlatform(self):
|
| + self.assertIsNone(_FinditForFracas().CheckPolicy(DummyCrashData(
|
| + platform = 'unsupported_platform')))
|
|
|
| - def _MockParse(*_):
|
| - stack = Stacktrace()
|
| - stack.append(CallStack(0))
|
| - return stack
|
| + def testCheckPolicyBlacklistedSignature(self):
|
| + self.assertIsNone(_FinditForFracas().CheckPolicy(DummyCrashData(
|
| + signature = 'Blacklist marker signature')))
|
|
|
| - def _MockGetDependencyRollsDict(*_):
|
| - return {'src/': DependencyRoll('src/', 'https://repo', '1', '2'),
|
| - 'src/add': DependencyRoll('src/add', 'https://repo1', None, '2'),
|
| - 'src/delete': DependencyRoll('src/delete', 'https://repo2',
|
| - '2', None)}
|
| + def testCheckPolicyPlatformRename(self):
|
| + new_crash_data = _FinditForFracas().CheckPolicy(DummyCrashData(
|
| + platform = 'linux'))
|
| + self.assertIsNotNone(new_crash_data,
|
| + 'FinditForFracas.CheckPolicy unexpectedly returned None')
|
| + self.assertEqual(new_crash_data['platform'], 'unix')
|
|
|
| - dummy_match_result = MatchResult(self.GetDummyChangeLog(), 'src/')
|
| - def _MockFindItForCrash(*args):
|
| - regression_deps_rolls = args[1]
|
| - if regression_deps_rolls:
|
| - return [dummy_match_result]
|
| + def testCreateAnalysis(self):
|
| + self.assertIsNotNone(_FinditForFracas().CreateAnalysis(
|
| + {'signature': 'sig'}))
|
|
|
| - return []
|
| + def testGetAnalysis(self):
|
| + crash_identifiers = {'signature': 'sig'}
|
| + # TODO(wrengr): would be less fragile to call
|
| + # FinditForFracas.CreateAnalysis instead.
|
| + analysis = FracasCrashAnalysis.Create(crash_identifiers)
|
| + analysis.put()
|
| + self.assertEqual(_FinditForFracas().GetAnalysis(crash_identifiers),
|
| + analysis)
|
|
|
| - def _MockComponentClassify(*_):
|
| - return []
|
| + def testInitializeAnalysisForFracas(self):
|
| + crash_data = DummyCrashData(platform = 'linux')
|
| + crash_identifiers = crash_data['crash_identifiers']
|
|
|
| - def _MockProjectClassify(*_):
|
| - return ''
|
| + findit_client = _FinditForFracas()
|
| + analysis = findit_client.CreateAnalysis(crash_identifiers)
|
| + findit_client._InitializeAnalysis(analysis, crash_data)
|
| + analysis.put()
|
| + analysis = findit_client.GetAnalysis(crash_identifiers)
|
| + self.assertIsNotNone(analysis,
|
| + 'FinditForFracas.GetAnalysis unexpectedly returned None')
|
|
|
| - findit_client = findit_for_chromecrash.FinditForChromeCrash(
|
| - git_repository.GitRepository(http_client=HttpClientAppengine()))
|
| + self.assertEqual(analysis.crashed_version, crash_data['crashed_version'])
|
| + self.assertEqual(analysis.signature, crash_data['signature'])
|
| + self.assertEqual(analysis.platform, crash_data['platform'])
|
| + self.assertEqual(analysis.stack_trace, crash_data['stack_trace'])
|
| + channel = crash_data['customized_data'].get('channel', None)
|
| + self.assertIsNotNone(channel,
|
| + 'channel is unexpectedly not defined in crash_data')
|
| + self.assertEqual(analysis.channel, channel)
|
| +
|
| + def testNeedsNewAnalysisIsTrueIfNoAnalysisYet(self):
|
| + self.assertTrue(_FinditForFracas()._NeedsNewAnalysis(DummyCrashData()))
|
|
|
| - self.mock(findit_client.dep_fetcher, 'GetDependency',
|
| - _MockGetDependency)
|
| - self.mock(chromecrash_parser.ChromeCrashParser, 'Parse', _MockParse)
|
| - self.mock(findit_client.dep_fetcher, 'GetDependencyRollsDict',
|
| - _MockGetDependencyRollsDict)
|
| - self.mock(findit_for_crash, 'FindItForCrash', _MockFindItForCrash)
|
| + def testNeedsNewAnalysisIsTrueIfLastOneFailed(self):
|
| + findit_client = _FinditForFracas()
|
| + crash_data = DummyCrashData()
|
| + analysis = findit_client.CreateAnalysis(crash_data['crash_identifiers'])
|
| + analysis.status = analysis_status.ERROR
|
| + analysis.put()
|
| + self.assertTrue(findit_client._NeedsNewAnalysis(crash_data))
|
|
|
| - self.mock(ComponentClassifier, 'Classify', _MockComponentClassify)
|
| - self.mock(ProjectClassifier, 'Classify', _MockProjectClassify)
|
| + def testNeedsNewAnalysisIsFalseIfLastOneIsNotFailed(self):
|
| + findit_client = _FinditForFracas()
|
| + crash_data = DummyCrashData()
|
| + crash_identifiers = crash_data['crash_identifiers']
|
| + for status in (analysis_status.PENDING, analysis_status.RUNNING,
|
| + analysis_status.COMPLETED, analysis_status.SKIPPED):
|
| + analysis = findit_client.CreateAnalysis(crash_identifiers)
|
| + analysis.status = status
|
| + analysis.put()
|
| + self.assertFalse(findit_client._NeedsNewAnalysis(crash_data))
|
|
|
| + def testScheduleNewAnalysisSkipsUnsupportedChannel(self):
|
| + self.assertFalse(_FinditForFracas().ScheduleNewAnalysis(DummyCrashData(
|
| + version = None,
|
| + signature = None,
|
| + crash_identifiers = {},
|
| + channel = 'unsupported_channel')))
|
| +
|
| + def testScheduleNewAnalysisSkipsUnsupportedPlatform(self):
|
| + self.assertFalse(_FinditForFracas().ScheduleNewAnalysis(DummyCrashData(
|
| + version = None,
|
| + signature = None,
|
| + platform = 'unsupported_platform',
|
| + crash_identifiers = {})))
|
| +
|
| + def testScheduleNewAnalysisSkipsBlackListSignature(self):
|
| + self.assertFalse(_FinditForFracas().ScheduleNewAnalysis(DummyCrashData(
|
| + version = None,
|
| + signature = 'Blacklist marker signature',
|
| + crash_identifiers = {})))
|
| +
|
| + def testScheduleNewAnalysisSkipsIfAlreadyCompleted(self):
|
| + findit_client = _FinditForFracas()
|
| + crash_data = DummyCrashData()
|
| + crash_identifiers = crash_data['crash_identifiers']
|
| + analysis = findit_client.CreateAnalysis(crash_identifiers)
|
| + analysis.status = analysis_status.COMPLETED
|
| + analysis.put()
|
| + self.assertFalse(findit_client.ScheduleNewAnalysis(crash_data))
|
| +
|
| + def testFindCulpritForChromeCrashEmptyStacktrace(self):
|
| + self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
|
| + 'GetDependency', lambda *_: {})
|
| + self.mock(ChromeCrashParser, 'Parse', lambda *_: Stacktrace())
|
| +
|
| + # TODO(wrengr): use NullCulprit instead
|
| expected_results = {'found': False}
|
| - expected_tag = {'found_suspects': False}
|
| + expected_tag = {'found_suspects': False,
|
| + 'has_regression_range': False}
|
|
|
| - results, tag = findit_client.FindCulprit(
|
| - 'signature', 'win', 'frame1\nframe2', '50.0.1234.0',
|
| - ['50.0.1233.0', '50.0.1234.0']).ToDicts()
|
| + analysis = CrashAnalysis()
|
| + analysis.signature = 'signature'
|
| + analysis.platform = 'win'
|
| + analysis.stack_trace = 'frame1\nframe2'
|
| + analysis.crashed_version = '50.0.1234.0'
|
| + analysis.historical_metadata = [
|
| + {'chrome_version': '51.0.1234.0', 'cpm': 0.6}]
|
| + results, tag = _FinditForChromeCrash().FindCulprit(analysis).ToDicts()
|
| +
|
| + self.assertDictEqual(expected_results, results)
|
| + self.assertDictEqual(expected_tag, tag)
|
| +
|
| + # TODO(http://crbug.com/659346): why do these mocks give coverage
|
| + # failures? That's almost surely hiding a bug in the tests themselves.
|
| + def testFindCulpritForChromeCrash(self): # pragma: no cover
|
| + self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
|
| + 'GetDependency', lambda *_: {})
|
| + self.mock(ChromeCrashParser, 'Parse', lambda *_: Stacktrace([CallStack(0)]))
|
| + self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
|
| + 'GetDependencyRollsDict',
|
| + lambda *_: {
|
| + 'src/': DependencyRoll('src/', 'https://repo', '1', '2'),
|
| + 'src/add': DependencyRoll('src/add', 'https://repo1', None, '2'),
|
| + 'src/delete': DependencyRoll('src/delete', 'https://repo2', '2',
|
| + None)
|
| + })
|
| +
|
| + dummy_match_result = MatchResult(self.GetDummyChangeLog(), 'src/')
|
| + self.mock(ChangelistClassifier, '__call__',
|
| + lambda _self, report:
|
| + [dummy_match_result] if report.regression_range else [])
|
| +
|
| + self.mock(ComponentClassifier, 'Classify', lambda *_: [])
|
| + self.mock(ProjectClassifier, 'Classify', lambda *_: '')
|
| +
|
| + # TODO(wrengr): for both these tests, we should compare Culprit
|
| + # objects directly rather than calling ToDicts and comparing the
|
| + # dictionaries.
|
| + self._testFindCulpritForChromeCrashSucceeds(dummy_match_result)
|
| + self._testFindCulpritForChromeCrashFails()
|
| +
|
| + def _testFindCulpritForChromeCrashSucceeds(self, dummy_match_result):
|
| + analysis = CrashAnalysis()
|
| + analysis.signature = 'signature'
|
| + analysis.platform = 'win'
|
| + analysis.stack_trace = 'frame1\nframe2'
|
| + analysis.crashed_version = '50.0.1234.0'
|
| + dummy_regression_range = ['50.0.1233.0', '50.0.1234.0']
|
| + analysis.regression_range = dummy_regression_range
|
| + results, tag = _FinditForChromeCrash().FindCulprit(analysis).ToDicts()
|
|
|
| - # TODO(wrengr): compare the Culprit object directly to these values,
|
| - # rather than converting to dicts first. We can make a different
|
| - # unit test for comparing the dicts, if we actually need/want to.
|
| expected_results = {
|
| 'found': True,
|
| 'suspected_project': '',
|
| 'suspected_components': [],
|
| 'suspected_cls': [dummy_match_result.ToDict()],
|
| - 'regression_range': ['50.0.1233.0', '50.0.1234.0']
|
| + 'regression_range': dummy_regression_range
|
| }
|
| expected_tag = {
|
| 'found_suspects': True,
|
| @@ -114,11 +288,16 @@ class FinditForChromeCrashTest(CrashTestCase):
|
| 'solution': 'core_algorithm',
|
| }
|
|
|
| - self.assertEqual(expected_results, results)
|
| - self.assertEqual(expected_tag, tag)
|
| + self.assertDictEqual(expected_results, results)
|
| + self.assertDictEqual(expected_tag, tag)
|
|
|
| - results, tag = findit_client.FindCulprit(
|
| - 'signature', 'win', 'frame1\nframe2', '50.0.1234.0', None).ToDicts()
|
| + def _testFindCulpritForChromeCrashFails(self):
|
| + analysis = CrashAnalysis()
|
| + analysis.signature = 'signature'
|
| + analysis.platform = 'win'
|
| + analysis.stack_trace = 'frame1\nframe2'
|
| + analysis.crashed_version = '50.0.1234.0'
|
| + results, tag = _FinditForChromeCrash().FindCulprit(analysis).ToDicts()
|
|
|
| expected_results = {
|
| 'found': False,
|
| @@ -135,11 +314,5 @@ class FinditForChromeCrashTest(CrashTestCase):
|
| 'solution': 'core_algorithm',
|
| }
|
|
|
| - self.assertEqual(expected_results, results)
|
| - self.assertEqual(expected_tag, tag)
|
| -
|
| - def testFieldsProperty(self):
|
| - culprit = findit_for_chromecrash.Culprit('proj', ['Blink>API'], [],
|
| - ['50.0.1234.0', '50.0.1234.1'])
|
| - self.assertEqual(culprit.fields,
|
| - ('project', 'components', 'cls', 'regression_range'))
|
| + self.assertDictEqual(expected_results, results)
|
| + self.assertDictEqual(expected_tag, tag)
|
|
|