def _testcase_reproduces_in_revision(testcase, testcase_file_path, job_type, revision, update_metadata=False): """Test to see if a test case reproduces in the specified revision.""" build_manager.setup_build(revision) if not build_manager.check_app_path(): raise errors.BuildSetupError(revision, job_type) if testcase_manager.check_for_bad_build(job_type, revision): log_message = 'Bad build at r%d. Skipping' % revision testcase = data_handler.get_testcase_by_id(testcase.key.id()) data_handler.update_testcase_comment(testcase, data_types.TaskState.WIP, log_message) raise errors.BadBuildError(revision, job_type) test_timeout = environment.get_value('TEST_TIMEOUT', 10) result = testcase_manager.test_for_crash_with_retries( testcase, testcase_file_path, test_timeout, http_flag=testcase.http_flag) _log_output(revision, result) if update_metadata: _update_issue_metadata(testcase) return result
def test_reproduce(self): """Test reproduce.""" testcase_file_path = os.path.join(self.temp_dir, 'testcase') with open(testcase_file_path, 'wb') as f: f.write(b'EEE') self._setup_env(job_type='libfuzzer_asan_job') build_manager.setup_build() result = testcase_manager.engine_reproduce(libfuzzer_engine.Engine(), 'test_fuzzer', testcase_file_path, [], 30) self.assertEqual([ os.path.join(environment.get_value('BUILD_DIR'), 'test_fuzzer'), '-runs=100', file_host.rebase_to_worker_root(testcase_file_path) ], result.command) self.assertEqual(result.return_code, libfuzzer_constants.TARGET_ERROR_EXITCODE) self.assertGreater(result.time_executed, 0) self.assertIn('Running 1 inputs 100 time(s) each', result.output) self.assertIn( 'AddressSanitizer: SEGV on unknown address 0x000000000000', result.output)
def test_target_not_found(self): """Test target not found.""" testcase_file_path = os.path.join(self.temp_dir, 'testcase') with open(testcase_file_path, 'wb') as f: f.write(b'EEE') self._setup_env(job_type='libfuzzer_asan_job') build_manager.setup_build() with self.assertRaises(testcase_manager.TargetNotFoundError): testcase_manager.engine_reproduce(libfuzzer_engine.Engine(), 'does_not_exist', testcase_file_path, [], 30)
def test_get_command_line_for_application(self): """Test get_command_line_for_application.""" self._setup_env(job_type='job') self.assertIsNotNone(build_manager.setup_build()) fuzz_inputs = os.environ['FUZZ_INPUTS'] file_to_run = os.path.join(fuzz_inputs, 'file_to_run') os.environ['APP_ARGS'] = '%TESTCASE% %TESTCASE_FILE_URL%' command_line = testcase_manager.get_command_line_for_application( file_to_run) app_path = os.environ['APP_PATH'] worker_fuzz_inputs = file_host.rebase_to_worker_root(fuzz_inputs) worker_file_to_run = os.path.join(worker_fuzz_inputs, 'file_to_run') self.assertEqual( command_line, '%s %s %s' % (app_path, worker_file_to_run, utils.file_path_to_file_url(worker_file_to_run))) launcher_path = os.path.join(os.environ['FUZZERS_DIR'], 'test', 'launcher.py') os.environ['LAUNCHER_PATH'] = launcher_path worker_launcher_path = file_host.rebase_to_worker_root(launcher_path) command_line = testcase_manager.get_command_line_for_application( file_to_run) self.assertEqual( command_line, '%s %s %s %s %s' % (sys.executable, worker_launcher_path, app_path, worker_file_to_run, worker_file_to_run))
def test_setup_regular_build_fuzz_target(self): """Test setting up a regular build.""" environment.set_value('TASK_NAME', 'fuzz') environment.set_value('TASK_ARGUMENT', 'libFuzzer') launcher_dir = os.path.join('src', 'clusterfuzz', '_internal', 'bot', 'fuzzers', 'libFuzzer') environment.set_value( 'FUZZER_DIR', os.path.join(os.environ['ROOT_DIR'], launcher_dir)) self._setup_env(job_type='libfuzzer_asan_job') build = build_manager.setup_build(target_weights={}) self.assertIsNotNone(build) worker_root_dir = os.environ['WORKER_ROOT_DIR'] expected_build_dir = os.path.join( worker_root_dir, 'bot', 'builds', 'clusterfuzz-test-data_test_libfuzzer_builds_' '41a87efdd470c6f00e8babf61548bf6c7de57137', 'revisions') self.assertEqual('', os.environ['APP_PATH']) self.assertEqual('1337', os.environ['APP_REVISION']) self.assertEqual('', os.environ['APP_PATH_DEBUG']) self.assertEqual(expected_build_dir, os.environ['BUILD_DIR']) self.assertEqual('', os.environ['APP_DIR']) self.assertEqual('test_fuzzer', os.environ['FUZZ_TARGET'])
def setup_build(testcase): """Set up a custom or regular build based on revision. For regular builds, if a provided revision is not found, set up a build with the closest revision <= provided revision.""" revision = testcase.crash_revision if revision and not build_manager.is_custom_binary(): build_bucket_path = build_manager.get_primary_bucket_path() revision_list = build_manager.get_revisions_list(build_bucket_path, testcase=testcase) if not revision_list: logs.log_error('Failed to fetch revision list.') return revision_index = revisions.find_min_revision_index( revision_list, revision) if revision_index is None: raise errors.BuildNotFoundError(revision, testcase.job_type) revision = revision_list[revision_index] build_manager.setup_build(revision)
def _testcase_reproduces_in_revision(testcase, testcase_file_path, job_type, revision, should_log=True, min_revision=None, max_revision=None): """Test to see if a test case reproduces in the specified revision.""" if should_log: log_message = 'Testing r%d' % revision if min_revision is not None and max_revision is not None: log_message += ' (current range %d:%d)' % (min_revision, max_revision) testcase = data_handler.get_testcase_by_id(testcase.key.id()) data_handler.update_testcase_comment(testcase, data_types.TaskState.WIP, log_message) build_manager.setup_build(revision) if not build_manager.check_app_path(): raise errors.BuildSetupError(revision, job_type) if testcase_manager.check_for_bad_build(job_type, revision): log_message = 'Bad build at r%d. Skipping' % revision testcase = data_handler.get_testcase_by_id(testcase.key.id()) data_handler.update_testcase_comment(testcase, data_types.TaskState.WIP, log_message) raise errors.BadBuildError(revision, job_type) test_timeout = environment.get_value('TEST_TIMEOUT', 10) result = testcase_manager.test_for_crash_with_retries( testcase, testcase_file_path, test_timeout, http_flag=testcase.http_flag) return result.is_crash()
def test_symbolize(self): """Test symbolize.""" self._setup_env(job_type='job') self.assertIsNotNone(build_manager.setup_build()) app_dir = environment.get_value('APP_DIR') unsymbolized_stacktrace = ( '#0 0x4f1eb4 ({0}/app+0x4f1eb4)\n' '#1 0x4f206e ({0}/app+0x4f206e)\n').format(app_dir) expected_symbolized_stacktrace = ( ' #0 0x4f1eb4 in Vuln(char*, unsigned long) /usr/local/google/home/' 'ochang/crashy_binary/test.cc:9:15\n' ' #1 0x4f206e in main /usr/local/google/home/ochang/crashy_binary/' 'test.cc:32:3\n') symbolized_stacktrace = symbolize_host.symbolize_stacktrace( unsymbolized_stacktrace) self.assertEqual(expected_symbolized_stacktrace, symbolized_stacktrace)
def test_setup_regular_build(self): """Test setting up a regular build.""" self._setup_env(job_type='job') build = build_manager.setup_build() self.assertIsNotNone(build) worker_root_dir = os.environ['WORKER_ROOT_DIR'] expected_build_dir = os.path.join( worker_root_dir, 'bot', 'builds', 'clusterfuzz-test-data_test_builds_' '2b6ddd7575e9b06b20306183720c65fff3ce318d', 'revisions') expected_app_dir = os.path.join( worker_root_dir, 'bot', 'builds', 'clusterfuzz-test-data_test_builds_' '2b6ddd7575e9b06b20306183720c65fff3ce318d', 'revisions', 'test_build') self.assertEqual(os.path.join(expected_app_dir, 'app'), os.environ['APP_PATH']) self.assertEqual('12345', os.environ['APP_REVISION']) self.assertEqual('', os.environ['APP_PATH_DEBUG']) self.assertEqual(expected_build_dir, os.environ['BUILD_DIR']) self.assertEqual(expected_app_dir, os.environ['APP_DIR']) self.assertEqual('', os.environ['FUZZ_TARGET'])
def _check_fixed_for_custom_binary(testcase, job_type, testcase_file_path): """Simplified fixed check for test cases using custom binaries.""" revision = environment.get_value('APP_REVISION') # Update comments to reflect bot information and clean up old comments. testcase_id = testcase.key.id() testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED) build_manager.setup_build() if not build_manager.check_app_path(): testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment( testcase, data_types.TaskState.ERROR, 'Build setup failed for custom binary') build_fail_wait = environment.get_value('FAIL_WAIT') tasks.add_task( 'progression', testcase_id, job_type, wait_time=build_fail_wait) return test_timeout = environment.get_value('TEST_TIMEOUT', 10) result = testcase_manager.test_for_crash_with_retries( testcase, testcase_file_path, test_timeout, http_flag=testcase.http_flag) _log_output(revision, result) # Re-fetch to finalize testcase updates in branches below. testcase = data_handler.get_testcase_by_id(testcase.key.id()) # If this still crashes on the most recent build, it's not fixed. The task # will be rescheduled by a cron job and re-attempted eventually. if result.is_crash(): app_path = environment.get_value('APP_PATH') command = testcase_manager.get_command_line_for_application( testcase_file_path, app_path=app_path, needs_http=testcase.http_flag) symbolized_crash_stacktrace = result.get_stacktrace(symbolized=True) unsymbolized_crash_stacktrace = result.get_stacktrace(symbolized=False) stacktrace = utils.get_crash_stacktrace_output( command, symbolized_crash_stacktrace, unsymbolized_crash_stacktrace) testcase.last_tested_crash_stacktrace = data_handler.filter_stacktrace( stacktrace) data_handler.update_progression_completion_metadata( testcase, revision, is_crash=True, message='still crashes on latest custom build') return if result.unexpected_crash: testcase.set_metadata( 'crashes_on_unexpected_state', True, update_testcase=False) else: testcase.delete_metadata( 'crashes_on_unexpected_state', update_testcase=False) # Retry once on another bot to confirm our results and in case this bot is in # a bad state which we didn't catch through our usual means. if data_handler.is_first_retry_for_task(testcase, reset_after_retry=True): tasks.add_task('progression', testcase_id, job_type) data_handler.update_progression_completion_metadata(testcase, revision) return # The bug is fixed. testcase.fixed = 'Yes' testcase.open = False data_handler.update_progression_completion_metadata( testcase, revision, message='fixed on latest custom build')
def execute_task(testcase_id, job_type): """Run a test case with a different job type to see if they reproduce.""" testcase = data_handler.get_testcase_by_id(testcase_id) if not testcase: return if (environment.is_engine_fuzzer_job(testcase.job_type) != environment.is_engine_fuzzer_job(job_type)): # We should never reach here. But in case we do, we should bail out as # otherwise we will run into exceptions. return # Use a cloned testcase entity with different fuzz target paramaters for # a different fuzzing engine. original_job_type = testcase.job_type testcase = _get_variant_testcase_for_job(testcase, job_type) # Setup testcase and its dependencies. file_list, _, testcase_file_path = setup.setup_testcase(testcase, job_type) if not file_list: return # Set up a custom or regular build. We explicitly omit the crash revision # since we want to test against the latest build here. try: build_manager.setup_build() except errors.BuildNotFoundError: logs.log_warn('Matching build not found.') return # Check if we have an application path. If not, our build failed to setup # correctly. if not build_manager.check_app_path(): testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment( testcase, data_types.TaskState.ERROR, 'Build setup failed with job: ' + job_type) return # Disable gestures if we're running on a different platform from that of # the original test case. use_gestures = testcase.platform == environment.platform().lower() # Reproduce the crash. app_path = environment.get_value('APP_PATH') command = testcase_manager.get_command_line_for_application( testcase_file_path, app_path=app_path, needs_http=testcase.http_flag) test_timeout = environment.get_value('TEST_TIMEOUT', 10) revision = environment.get_value('APP_REVISION') result = testcase_manager.test_for_crash_with_retries( testcase, testcase_file_path, test_timeout, http_flag=testcase.http_flag, use_gestures=use_gestures, compare_crash=False) if result.is_crash() and not result.should_ignore(): crash_state = result.get_state() crash_type = result.get_type() security_flag = result.is_security_issue() gestures = testcase.gestures if use_gestures else None one_time_crasher_flag = not testcase_manager.test_for_reproducibility( testcase.fuzzer_name, testcase.actual_fuzzer_name(), testcase_file_path, crash_state, security_flag, test_timeout, testcase.http_flag, gestures) if one_time_crasher_flag: status = data_types.TestcaseVariantStatus.FLAKY else: status = data_types.TestcaseVariantStatus.REPRODUCIBLE crash_comparer = CrashComparer(crash_state, testcase.crash_state) is_similar = (crash_comparer.is_similar() and security_flag == testcase.security_flag) unsymbolized_crash_stacktrace = result.get_stacktrace(symbolized=False) symbolized_crash_stacktrace = result.get_stacktrace(symbolized=True) crash_stacktrace_output = utils.get_crash_stacktrace_output( command, symbolized_crash_stacktrace, unsymbolized_crash_stacktrace) else: status = data_types.TestcaseVariantStatus.UNREPRODUCIBLE is_similar = False crash_type = None crash_state = None security_flag = False crash_stacktrace_output = 'No crash occurred.' if original_job_type == job_type: # This case happens when someone clicks 'Update last tested stacktrace using # trunk build' button. testcase = data_handler.get_testcase_by_id(testcase_id) testcase.last_tested_crash_stacktrace = ( data_handler.filter_stacktrace(crash_stacktrace_output)) testcase.set_metadata('last_tested_crash_revision', revision, update_testcase=True) else: # Regular case of variant analysis. variant = data_handler.get_testcase_variant(testcase_id, job_type) variant.status = status variant.revision = revision variant.crash_type = crash_type variant.crash_state = crash_state variant.security_flag = security_flag variant.is_similar = is_similar variant.platform = environment.platform().lower() # Explicitly skipping crash stacktrace for now as it make entities larger # and we plan to use only crash paramaters in UI. variant.put()
def do_corpus_pruning(context, last_execution_failed, revision): """Run corpus pruning.""" # Set |FUZZ_TARGET| environment variable to help with unarchiving only fuzz # target and its related files. environment.set_value('FUZZ_TARGET', context.fuzz_target.binary) if environment.is_trusted_host(): from clusterfuzz._internal.bot.untrusted_runner import tasks_host return tasks_host.do_corpus_pruning(context, last_execution_failed, revision) if not build_manager.setup_build(revision=revision): raise CorpusPruningException('Failed to setup build.') build_directory = environment.get_value('BUILD_DIR') start_time = datetime.datetime.utcnow() runner = Runner(build_directory, context) pruner = CorpusPruner(runner) fuzzer_binary_name = os.path.basename(runner.target_path) # If our last execution failed, shrink to a randomized corpus of usable size # to prevent corpus from growing unbounded and recurring failures when trying # to minimize it. if last_execution_failed: for corpus_url in [ context.corpus.get_gcs_url(), context.quarantine_corpus.get_gcs_url() ]: _limit_corpus_size(corpus_url) # Get initial corpus to process from GCS. context.sync_to_disk() initial_corpus_size = shell.get_directory_file_count( context.initial_corpus_path) # Restore a small batch of quarantined units back to corpus. context.restore_quarantined_units() # Shrink to a minimized corpus using corpus merge. pruner_stats = pruner.run(context.initial_corpus_path, context.minimized_corpus_path, context.bad_units_path) # Sync minimized corpus back to GCS. context.sync_to_gcs() # Create corpus backup. # Temporarily copy the past crash regressions folder into the minimized corpus # so that corpus backup archive can have both. regressions_input_dir = os.path.join(context.initial_corpus_path, 'regressions') regressions_output_dir = os.path.join(context.minimized_corpus_path, 'regressions') if shell.get_directory_file_count(regressions_input_dir): shutil.copytree(regressions_input_dir, regressions_output_dir) backup_bucket = environment.get_value('BACKUP_BUCKET') corpus_backup_url = corpus_manager.backup_corpus( backup_bucket, context.corpus, context.minimized_corpus_path) shell.remove_directory(regressions_output_dir) minimized_corpus_size_units = shell.get_directory_file_count( context.minimized_corpus_path) minimized_corpus_size_bytes = shell.get_directory_size( context.minimized_corpus_path) logs.log('Corpus pruned from %d to %d units.' % (initial_corpus_size, minimized_corpus_size_units)) # Process bad units found during merge. # Mapping of crash state -> CorpusCrash crashes = {} pruner.process_bad_units(context.bad_units_path, context.quarantine_corpus_path, crashes) context.quarantine_corpus.rsync_from_disk(context.quarantine_corpus_path) # Store corpus stats into CoverageInformation entity. project_qualified_name = context.fuzz_target.project_qualified_name() today = datetime.datetime.utcnow().date() coverage_info = data_types.CoverageInformation( fuzzer=project_qualified_name, date=today) quarantine_corpus_size = shell.get_directory_file_count( context.quarantine_corpus_path) quarantine_corpus_dir_size = shell.get_directory_size( context.quarantine_corpus_path) # Save the minimize corpus size before cross pollination to put in BigQuery. pre_pollination_corpus_size = minimized_corpus_size_units # Populate coverage stats. coverage_info.corpus_size_units = minimized_corpus_size_units coverage_info.corpus_size_bytes = minimized_corpus_size_bytes coverage_info.quarantine_size_units = quarantine_corpus_size coverage_info.quarantine_size_bytes = quarantine_corpus_dir_size coverage_info.corpus_backup_location = corpus_backup_url coverage_info.corpus_location = context.corpus.get_gcs_url() coverage_info.quarantine_location = context.quarantine_corpus.get_gcs_url() # Calculate remaining time to use for shared corpus merging. time_remaining = _get_time_remaining(start_time) if time_remaining <= 0: logs.log_warn('Not enough time for shared corpus merging.') return None cross_pollinator = CrossPollinator(runner) pollinator_stats = cross_pollinator.run(time_remaining) context.sync_to_gcs() # Update corpus size stats. minimized_corpus_size_units = shell.get_directory_file_count( context.minimized_corpus_path) minimized_corpus_size_bytes = shell.get_directory_size( context.minimized_corpus_path) coverage_info.corpus_size_units = minimized_corpus_size_units coverage_info.corpus_size_bytes = minimized_corpus_size_bytes logs.log('Finished.') sources = ','.join([ fuzzer.fuzz_target.project_qualified_name() for fuzzer in context.cross_pollinate_fuzzers ]) cross_pollination_stats = None if pruner_stats and pollinator_stats: cross_pollination_stats = CrossPollinationStats( project_qualified_name, context.cross_pollination_method, sources, context.tag, initial_corpus_size, pre_pollination_corpus_size, pruner_stats['edge_coverage'], pollinator_stats['edge_coverage'], pruner_stats['feature_coverage'], pollinator_stats['feature_coverage']) return CorpusPruningResult(coverage_info=coverage_info, crashes=list(crashes.values()), fuzzer_binary_name=fuzzer_binary_name, revision=environment.get_value('APP_REVISION'), cross_pollination_stats=cross_pollination_stats)
def execute_task(testcase_id, job_type): """Execute a symbolize command.""" # Locate the testcase associated with the id. testcase = data_handler.get_testcase_by_id(testcase_id) # We should atleast have a symbolized debug or release build. if not build_manager.has_symbolized_builds(): return data_handler.update_testcase_comment(testcase, data_types.TaskState.STARTED) # Setup testcase and its dependencies. file_list, _, testcase_file_path = setup.setup_testcase(testcase, job_type) if not file_list: return # Initialize variables. build_fail_wait = environment.get_value('FAIL_WAIT') old_crash_stacktrace = data_handler.get_stacktrace(testcase) sym_crash_type = testcase.crash_type sym_crash_address = testcase.crash_address sym_crash_state = testcase.crash_state sym_redzone = DEFAULT_REDZONE warmup_timeout = environment.get_value('WARMUP_TIMEOUT') # Decide which build revision to use. if testcase.crash_stacktrace == 'Pending': # This usually happen when someone clicked the 'Update stacktrace from # trunk' button on the testcase details page. In this case, we are forced # to use trunk. No revision -> trunk build. build_revision = None else: build_revision = testcase.crash_revision # Set up a custom or regular build based on revision. build_manager.setup_build(build_revision) # Get crash revision used in setting up build. crash_revision = environment.get_value('APP_REVISION') if not build_manager.check_app_path(): testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Build setup failed') tasks.add_task('symbolize', testcase_id, job_type, wait_time=build_fail_wait) return # ASAN tool settings (if the tool is used). # See if we can get better stacks with higher redzone sizes. # A UAF might actually turn out to be OOB read/write with a bigger redzone. if environment.tool_matches('ASAN', job_type) and testcase.security_flag: redzone = MAX_REDZONE while redzone >= MIN_REDZONE: environment.reset_current_memory_tool_options( redzone_size=testcase.redzone, disable_ubsan=testcase.disable_ubsan) process_handler.terminate_stale_application_instances() command = testcase_manager.get_command_line_for_application( testcase_file_path, needs_http=testcase.http_flag) return_code, crash_time, output = (process_handler.run_process( command, timeout=warmup_timeout, gestures=testcase.gestures)) crash_result = CrashResult(return_code, crash_time, output) if crash_result.is_crash() and 'AddressSanitizer' in output: state = crash_result.get_symbolized_data() security_flag = crash_result.is_security_issue() if (not crash_analyzer.ignore_stacktrace( state.crash_stacktrace) and security_flag == testcase.security_flag and state.crash_type == testcase.crash_type and (state.crash_type != sym_crash_type or state.crash_state != sym_crash_state)): logs.log( 'Changing crash parameters.\nOld : %s, %s, %s' % (sym_crash_type, sym_crash_address, sym_crash_state)) sym_crash_type = state.crash_type sym_crash_address = state.crash_address sym_crash_state = state.crash_state sym_redzone = redzone old_crash_stacktrace = state.crash_stacktrace logs.log( '\nNew : %s, %s, %s' % (sym_crash_type, sym_crash_address, sym_crash_state)) break redzone /= 2 # We should have atleast a symbolized debug or a release build. symbolized_builds = build_manager.setup_symbolized_builds(crash_revision) if (not symbolized_builds or (not build_manager.check_app_path() and not build_manager.check_app_path('APP_PATH_DEBUG'))): testcase = data_handler.get_testcase_by_id(testcase_id) data_handler.update_testcase_comment(testcase, data_types.TaskState.ERROR, 'Build setup failed') tasks.add_task('symbolize', testcase_id, job_type, wait_time=build_fail_wait) return # Increase malloc_context_size to get all stack frames. Default is 30. environment.reset_current_memory_tool_options( redzone_size=sym_redzone, malloc_context_size=STACK_FRAME_COUNT, symbolize_inline_frames=True, disable_ubsan=testcase.disable_ubsan) # TSAN tool settings (if the tool is used). if environment.tool_matches('TSAN', job_type): environment.set_tsan_max_history_size() # Do the symbolization if supported by this application. result, sym_crash_stacktrace = (get_symbolized_stacktraces( testcase_file_path, testcase, old_crash_stacktrace, sym_crash_state)) # Update crash parameters. testcase = data_handler.get_testcase_by_id(testcase_id) testcase.crash_type = sym_crash_type testcase.crash_address = sym_crash_address testcase.crash_state = sym_crash_state testcase.crash_stacktrace = ( data_handler.filter_stacktrace(sym_crash_stacktrace)) if not result: data_handler.update_testcase_comment( testcase, data_types.TaskState.ERROR, 'Unable to reproduce crash, skipping ' 'stacktrace update') else: # Switch build url to use the less-optimized symbolized build with better # stacktrace. build_url = environment.get_value('BUILD_URL') if build_url: testcase.set_metadata('build_url', build_url, update_testcase=False) data_handler.update_testcase_comment(testcase, data_types.TaskState.FINISHED) testcase.symbolized = True testcase.crash_revision = crash_revision testcase.put() # We might have updated the crash state. See if we need to marked as duplicate # based on other testcases. data_handler.handle_duplicate_entry(testcase) task_creation.create_blame_task_if_needed(testcase) # Switch current directory before builds cleanup. root_directory = environment.get_value('ROOT_DIR') os.chdir(root_directory) # Cleanup symbolized builds which are space-heavy. symbolized_builds.delete()