def notify_uploader_when_testcase_is_processed(policy, testcase, issue): """Notify uploader by email when all the testcase tasks are finished.""" testcase_id = testcase.key.id() # Check if this is a user upload. If not, bail out. upload_metadata = data_types.TestcaseUploadMetadata.query( data_types.TestcaseUploadMetadata.testcase_id == testcase_id).get() if not upload_metadata: return # Check that we have a valid email to send the notification. If not, bail out. to_email = upload_metadata.uploader_email if not to_email: return # If this is a bundled archive with multiple testcases, then don't send email # for individual testcases. if upload_metadata.bundled: return # Check if the notification is already sent once. If yes, bail out. if data_handler.is_notification_sent(testcase_id, to_email): return # Make sure all testcase taks are done (e.g. minimization, regression, etc). if not data_handler.critical_tasks_completed(testcase): return notify = not upload_metadata.quiet_flag if issue: issue_description = data_handler.get_issue_description(testcase) _update_issue_when_uploaded_testcase_is_processed( policy, testcase, issue, issue_description, upload_metadata.bug_summary_update_flag, notify, ) if notify: issue_description_without_crash_state = data_handler.get_issue_description( testcase, hide_crash_state=True) _send_email_to_uploader(testcase_id, to_email, issue_description_without_crash_state) # Make sure to create notification entry, as we use this to update bug. data_handler.create_notification_entry(testcase_id, to_email)
def test_get_issue_description_timeout(self): """Test get_issue_description for a timeout testcase.""" self.mock.get().name = 'chromium' self.testcase.crash_type = 'Timeout' self.testcase.crash_stacktrace = ( 'Line1\n' 'Command: /fuzzer -rss_limit_mb=2048 -timeout=25 -max_len=10 /testcase' ) self.testcase.put() description = data_handler.get_issue_description(self.testcase) self.assertEqual( description, 'Detailed report: https://test-clusterfuzz.appspot.com/' 'testcase?key=1\n\n' 'Fuzzer: libfuzzer_binary_name\n' 'Fuzz target binary: binary_name\n' 'Job Type: linux_asan_chrome\n' 'Crash Type: Timeout (exceeds 25 secs)\n' 'Crash Address: 0x1337\n' 'Crash State:\n A\n B\n C\n \n' 'Sanitizer: address (ASAN)\n\n' 'Reproducer Testcase: ' 'https://test-clusterfuzz.appspot.com/download?testcase_id=1\n\n' 'See help_url for instructions to reproduce this bug locally.')
def test_get_issue_description_oom(self): """Test get_issue_description for an oom testcase.""" self.mock.get().name = 'chromium' self.testcase.crash_type = 'Out-of-memory' self.testcase.crash_stacktrace = ( 'Line1\n' 'Command: /fuzzer -rss_limit_mb=2048 -timeout=25 -max_len=10 /testcase' ) self.testcase.job_type = 'windows_asan_chrome' self.testcase.one_time_crasher_flag = True self.testcase.second_crash_stacktrace = 'No crash using abc job type.' self.testcase.put() description = data_handler.get_issue_description(self.testcase) self.assertEqual( description, 'Detailed report: https://test-clusterfuzz.appspot.com/' 'testcase?key=1\n\n' 'Fuzzer: libfuzzer_binary_name\n' 'Fuzz target binary: binary_name\n' 'Job Type: windows_asan_chrome\n' 'Crash Type: Out-of-memory (exceeds 2048 MB)\n' 'Crash Address: 0x1337\n' 'Crash State:\n A\n B\n C\n \n' 'Sanitizer: address (ASAN)\n\n' 'Reproducer Testcase: ' 'https://test-clusterfuzz.appspot.com/download?testcase_id=1\n\n' 'No crash using abc job type.\n\n' 'See help_url for instructions to reproduce this bug locally.\n\n' '%s' % data_handler.FILE_UNREPRODUCIBLE_TESTCASE_TEXT)
def _add_issue_comment(testcase, comment): """Helper function to add a comment to the bug associated with a test case.""" if not testcase.bug_information: return # Populate the full message. report = data_handler.get_issue_description(testcase).rstrip('\n') full_comment = '%s\n\n%s\n\n%s' % (comment, report, FIXED_REPORT_FOOTER) # Update the issue. issue_tracker_manager = ( issue_tracker_utils.get_issue_tracker_manager(testcase)) issue = issue_tracker_manager.get_issue(int(testcase.bug_information)) issue.comment = full_comment issue_tracker_manager.save(issue, send_email=True)
def test_get_issue_description_different_project(self): """Test get_issue_description with a differing project name.""" self.mock.default_project_name.return_value = 'oss-fuzz' self.mock.get().url = 'url' description = data_handler.get_issue_description(self.testcase) self.assertEqual( description, 'Detailed report: https://test-clusterfuzz.appspot.com/' 'testcase?key=1\n\n' 'Project: project\n' 'Fuzzer: libfuzzer_binary_name\n' 'Fuzz target binary: binary_name\n' 'Job Type: linux_asan_chrome\n' 'Crash Type: Crash-type\n' 'Crash Address: 0x1337\n' 'Crash State:\n A\n B\n C\n \n' 'Sanitizer: address (ASAN)\n\n' 'Reproducer Testcase: ' 'https://test-clusterfuzz.appspot.com/download?testcase_id=1\n\n' 'See help_url for instructions to reproduce this bug locally.')
def test_get_issue_description_blackbox_fuzzer_testcase(self): """Test get_issue_description with a blackbox fuzzer testcase.""" self.mock.default_project_name.return_value = 'oss-fuzz' self.mock.get().url = 'url' description = data_handler.get_issue_description(self.testcase_null) self.assertEqual( description, 'Detailed report: https://test-clusterfuzz.appspot.com/' 'testcase?key=3\n\n' 'Project: project\n' 'Fuzzer: fuzzer1\n' 'Job Type: linux_asan_chrome\n' 'Crash Type: UNKNOWN\n' 'Crash Address: 0x1337\n' 'Crash State:\n NULL\n' 'Sanitizer: address (ASAN)\n\n' 'Reproducer Testcase: https://test-clusterfuzz.appspot.com/' 'download?testcase_id=3\n\n' 'See help_url for instructions to reproduce this bug locally.')
def update_issue(testcase, issue_id, needs_summary_update): """Associate (or update) an existing issue with the testcase.""" issue_id = helpers.cast(issue_id, int, 'Issue ID (%s) is not a number!' % issue_id) issue_tracker = helpers.get_issue_tracker_for_testcase(testcase) issue = helpers.get_or_exit( lambda: issue_tracker.get_issue(issue_id), 'Issue (id=%d) is not found!' % issue_id, 'Failed to get the issue (id=%s).' % issue_id, Exception) if not issue.is_open: raise helpers.EarlyExitException( ('The issue (%d) is already closed and further updates are not' ' allowed. Please file a new issue instead!') % issue_id, 400) if not testcase.is_crash(): raise helpers.EarlyExitException( 'This is not a crash testcase, so issue update is not applicable.', 400) issue_comment = data_handler.get_issue_description( testcase, helpers.get_user_email()) if needs_summary_update: issue.title = data_handler.get_issue_summary(testcase) policy = issue_tracker_policy.get(issue_tracker.project) properties = policy.get_existing_issue_properties() for label in properties.labels: for result in issue_filer.apply_substitutions( policy, label, testcase): issue.labels.add(result) issue.save(new_comment=issue_comment) testcase.bug_information = str(issue_id) testcase.put() data_handler.update_group_bug(testcase.group_id) helpers.log('Updated issue %sd' % issue_id, helpers.MODIFY_OPERATION)
def update_issue(testcase, issue_id, needs_summary_update): """Associate (or update) an existing issue with the testcase.""" issue_id = helpers.cast(issue_id, int, 'Issue ID (%s) is not a number!' % issue_id) itm = helpers.get_issue_tracker_manager(testcase) issue = helpers.get_or_exit( lambda: itm.get_issue(issue_id), 'Issue (id=%d) is not found!' % issue_id, 'Failed to get the issue (id=%s).' % issue_id, Exception) if not issue.open: raise helpers.EarlyExitException( ('The issue (%d) is already closed and further updates are not' ' allowed. Please file a new issue instead!') % issue_id, 400) # Create issue parameters. issue.comment = data_handler.get_issue_description( testcase, helpers.get_user_email()) issue_summary = data_handler.get_issue_summary(testcase) # NULL states leads to unhelpful summaries, so do not update in that case. if needs_summary_update and testcase.crash_state != 'NULL': issue.summary = issue_summary # Add label on memory tool used. issue_filer.add_memory_tool_label_if_needed(issue, testcase) # Add view restrictions for internal job types. issue_filer.add_view_restrictions_if_needed(issue, testcase) # Don't enforce security severity label on an existing issue. itm.save(issue) testcase.bug_information = str(issue_id) testcase.put() data_handler.update_group_bug(testcase.group_id) helpers.log('Updated issue %sd' % issue_id, helpers.MODIFY_OPERATION)
def test_get_issue_description_additional_issue_fields(self): """Test get_issue_description with additional fields set in metadata.""" self.mock.get().name = 'chromium' self.testcase.crash_type = 'Out-of-memory' self.testcase.crash_stacktrace = ( 'Line1\n' 'Command: /fuzzer -rss_limit_mb=2048 -timeout=25 -max_len=10 /testcase' ) self.testcase.job_type = 'windows_asan_chrome' self.testcase.one_time_crasher_flag = True self.testcase.set_metadata( 'issue_metadata', { 'additional_fields': { 'Acknowledgements': ['Alice', 'Bob', 'Eve', 'Mallory'], 'Answer': 42, } }) self.testcase.put() description = data_handler.get_issue_description(self.testcase) self.assertEqual( description, 'Detailed Report: https://test-clusterfuzz.appspot.com/' 'testcase?key=1\n\n' 'Fuzzing Engine: libFuzzer\n' 'Fuzz Target: binary_name\n' 'Job Type: windows_asan_chrome\n' 'Crash Type: Out-of-memory (exceeds 2048 MB)\n' 'Crash Address: 0x1337\n' 'Crash State:\n A\n B\n C\n \n' 'Sanitizer: address (ASAN)\n\n' 'Crash Revision: https://test-clusterfuzz.appspot.com/revisions?' 'job=windows_asan_chrome&revision=1337\n\n' 'Reproducer Testcase: ' 'https://test-clusterfuzz.appspot.com/download?testcase_id=1\n\n' 'See help_url for instructions to reproduce this bug locally.\n\n' '%s\n\n' 'Acknowledgements: [\'Alice\', \'Bob\', \'Eve\', \'Mallory\']\n' 'Answer: 42' % data_handler.FILE_UNREPRODUCIBLE_TESTCASE_TEXT)
def file_issue(testcase, issue_tracker, security_severity=None, user_email=None, additional_ccs=None): """File an issue for the given test case.""" logs.log('Filing new issue for testcase: %d' % testcase.key.id()) policy = issue_tracker_policy.get(issue_tracker.project) is_crash = not utils.sub_string_exists_in(NON_CRASH_TYPES, testcase.crash_type) properties = policy.get_new_issue_properties( is_security=testcase.security_flag, is_crash=is_crash) issue = issue_tracker.new_issue() issue.title = data_handler.get_issue_summary(testcase) issue.body = data_handler.get_issue_description(testcase, reporter=user_email, show_reporter=True) # Add reproducibility flag label. if testcase.one_time_crasher_flag: issue.labels.add(policy.label('unreproducible')) else: issue.labels.add(policy.label('reproducible')) # Chromium-specific labels. if issue_tracker.project == 'chromium' and testcase.security_flag: # Add reward labels if this is from an external fuzzer contribution. fuzzer = data_types.Fuzzer.query( data_types.Fuzzer.name == testcase.fuzzer_name).get() if fuzzer and fuzzer.external_contribution: issue.labels.add('reward-topanel') issue.labels.add('External-Fuzzer-Contribution') update_issue_impact_labels(testcase, issue) # Add additional labels from the job definition and fuzzer. additional_labels = data_handler.get_additional_values_for_variable( 'AUTOMATIC_LABELS', testcase.job_type, testcase.fuzzer_name) for label in additional_labels: issue.labels.add(label) # Add additional components from the job definition and fuzzer. automatic_components = data_handler.get_additional_values_for_variable( 'AUTOMATIC_COMPONENTS', testcase.job_type, testcase.fuzzer_name) for component in automatic_components: issue.components.add(component) # Add issue assignee from the job definition and fuzzer. automatic_assignee = data_handler.get_additional_values_for_variable( 'AUTOMATIC_ASSIGNEE', testcase.job_type, testcase.fuzzer_name) if automatic_assignee: issue.status = policy.status('assigned') issue.assignee = automatic_assignee[0] else: issue.status = properties.status # Add additional ccs from the job definition and fuzzer. ccs = data_handler.get_additional_values_for_variable( 'AUTOMATIC_CCS', testcase.job_type, testcase.fuzzer_name) # For externally contributed fuzzers, potentially cc the author. # Use fully qualified fuzzer name if one is available. fully_qualified_fuzzer_name = (testcase.overridden_fuzzer_name or testcase.fuzzer_name) ccs += external_users.cc_users_for_fuzzer(fully_qualified_fuzzer_name, testcase.security_flag) ccs += external_users.cc_users_for_job(testcase.job_type, testcase.security_flag) # Add the user as a cc if requested, and any default ccs for this job. # Check for additional ccs or labels from the job definition. if additional_ccs: ccs += [cc for cc in additional_ccs if cc not in ccs] # For user uploads, we assume the uploader is interested in the issue. if testcase.uploader_email and testcase.uploader_email not in ccs: ccs.append(testcase.uploader_email) ccs.extend(properties.ccs) # Get view restriction rules for the job. issue_restrictions = data_handler.get_value_from_job_definition( testcase.job_type, 'ISSUE_VIEW_RESTRICTIONS', 'security') should_restrict_issue = (issue_restrictions == 'all' or (issue_restrictions == 'security' and testcase.security_flag)) has_accountable_people = bool(ccs) # Check for labels with special logic. additional_labels = [] if should_restrict_issue: additional_labels.append(policy.label('restrict_view')) if has_accountable_people: additional_labels.append(policy.label('reported')) if testcase.security_flag: additional_labels.append(policy.label('security_severity')) additional_labels.append(policy.label('os')) # Apply label substitutions. for label in itertools.chain(properties.labels, additional_labels): for result in apply_substitutions(policy, label, testcase, security_severity): issue.labels.add(result) issue.body += data_handler.format_issue_information( testcase, properties.issue_body_footer) if (should_restrict_issue and has_accountable_people and policy.deadline_policy_message): issue.body += '\n\n' + policy.deadline_policy_message for cc in ccs: issue.ccs.add(cc) # Add additional labels and components from testcase metadata. metadata_labels = _get_from_metadata(testcase, 'issue_labels') for label in metadata_labels: issue.labels.add(label) metadata_components = _get_from_metadata(testcase, 'issue_components') for component in metadata_components: issue.components.add(component) issue.reporter = user_email issue.save() # Update the testcase with this newly created issue. testcase.bug_information = str(issue.id) testcase.put() data_handler.update_group_bug(testcase.group_id) return issue.id
def file_issue(testcase, itm, security_severity=None, user_email=None, additional_ccs=None): """File an issue for the given test case.""" issue = Issue() issue.summary = data_handler.get_issue_summary(testcase) issue.body = data_handler.get_issue_description( testcase, reporter=user_email, show_reporter=True) # Labels applied by default across all issue trackers. issue.status = 'New' issue.add_label('ClusterFuzz') # Add label on memory tool used. add_memory_tool_label_if_needed(issue, testcase) # Add reproducibility flag label. if testcase.one_time_crasher_flag: issue.add_label('Unreproducible') else: issue.add_label('Reproducible') # Add security severity flag label. add_security_severity_label_if_needed(issue, testcase, security_severity) # Get view restriction rules for the job. issue_restrictions = data_handler.get_value_from_job_definition( testcase.job_type, 'ISSUE_VIEW_RESTRICTIONS', 'security') should_restrict_issue = ( issue_restrictions == 'all' or (issue_restrictions == 'security' and testcase.security_flag)) # Chromium-specific labels. if itm.project_name == 'chromium': # A different status system is used on the chromium tracker. Since we # have already reproduced the crash, we skip the Unconfirmed status. issue.status = 'Untriaged' # Add OS label. if environment.is_chromeos_job(testcase.job_type): # ChromeOS fuzzers run on Linux platform, so use correct OS-Chrome for # tracking. issue.add_label('OS-Chrome') elif testcase.platform_id: os_label = 'OS-%s' % ((testcase.platform_id.split(':')[0]).capitalize()) issue.add_label(os_label) # Add view restrictions for internal job types. add_view_restrictions_if_needed(issue, testcase) if testcase.security_flag: # Apply labels specific to security bugs. issue.add_label('Restrict-View-SecurityTeam') issue.add_label('Type-Bug-Security') # Add reward labels if this is from an external fuzzer contribution. fuzzer = data_types.Fuzzer.query( data_types.Fuzzer.name == testcase.fuzzer_name).get() if fuzzer and fuzzer.external_contribution: issue.add_label('reward-topanel') issue.add_label('External-Fuzzer-Contribution') data_handler.update_issue_impact_labels(testcase, issue) else: # Apply labels for functional (non-security) bugs. if utils.sub_string_exists_in(NON_CRASH_TYPES, testcase.crash_type): # Non-crashing test cases shouldn't be assigned Pri-1. issue.add_label('Pri-2') issue.add_label('Type-Bug') else: # Default functional bug labels. issue.add_label('Pri-1') issue.add_label('Stability-Crash') issue.add_label('Type-Bug') # AOSP-specific labels. elif itm.project_name == 'android': if testcase.security_flag: # Security bug labels. issue.add_cc('*****@*****.**') issue.add_label('Type-Security') issue.add_label('Restrict-View-Commit') else: # Functional bug labels. issue.add_label('Type-Defect') # OSS-Fuzz specific labels. elif itm.project_name == 'oss-fuzz': if testcase.security_flag: # Security bug labels. issue.add_label('Type-Bug-Security') else: # Functional bug labels. issue.add_label('Type-Bug') if should_restrict_issue: issue.add_label('Restrict-View-Commit') # Add additional labels from the job definition and fuzzer. additional_labels = data_handler.get_additional_values_for_variable( 'AUTOMATIC_LABELS', testcase.job_type, testcase.fuzzer_name) for label in additional_labels: issue.add_label(label) # Add additional components from the job definition and fuzzer. automatic_components = data_handler.get_additional_values_for_variable( 'AUTOMATIC_COMPONENTS', testcase.job_type, testcase.fuzzer_name) for component in automatic_components: issue.add_component(component) # Add additional ccs from the job definition and fuzzer. ccs = data_handler.get_additional_values_for_variable( 'AUTOMATIC_CCS', testcase.job_type, testcase.fuzzer_name) # For externally contributed fuzzers, potentially cc the author. # Use fully qualified fuzzer name if one is available. fully_qualified_fuzzer_name = ( testcase.overridden_fuzzer_name or testcase.fuzzer_name) ccs += external_users.cc_users_for_fuzzer(fully_qualified_fuzzer_name, testcase.security_flag) ccs += external_users.cc_users_for_job(testcase.job_type, testcase.security_flag) # Add the user as a cc if requested, and any default ccs for this job. # Check for additional ccs or labels from the job definition. if additional_ccs: ccs += [cc for cc in additional_ccs if cc not in ccs] # For user uploads, we assume the uploader is interested in the issue. if testcase.uploader_email and testcase.uploader_email not in ccs: ccs.append(testcase.uploader_email) if itm.project_name == 'oss-fuzz' and ccs: # Add a reported label for deadline tracking. issue.add_label(reported_label()) if issue.has_label_matching('Restrict-View-Commit'): issue.body += '\n\n' + DEADLINE_NOTE issue.body += '\n\n' + FIX_NOTE issue.body += '\n\n' + QUESTIONS_NOTE for cc in ccs: issue.add_cc(cc) # Add additional labels from testcase metadata. metadata_labels = utils.parse_delimited( testcase.get_metadata('issue_labels', ''), delimiter=',', strip=True, remove_empty=True) for label in metadata_labels: issue.add_label(label) issue.itm = itm issue.reporter = user_email issue.save() # Update the testcase with this newly created issue. testcase.bug_information = str(issue.id) testcase.put() data_handler.update_group_bug(testcase.group_id) return issue.id