def _reproduce_crash(testcase_url, build_directory): """Reproduce a crash.""" _prepare_initial_environment(build_directory) # Validate the test case URL and fetch the tool's configuration. testcase_id = _get_testcase_id_from_url(testcase_url) configuration = config.ReproduceToolConfiguration(testcase_url) testcase = _get_testcase(testcase_id, configuration) testcase_path = _download_testcase(testcase_id, testcase, configuration) _update_environment_for_testcase(testcase, build_directory) # Validate that we're running on the right platform for this test case. platform = environment.platform().lower() if testcase.platform == 'android' and platform == 'linux': _prepare_environment_for_android() elif testcase.platform == 'android' and platform != 'linux': raise errors.ReproduceToolUnrecoverableError( 'The ClusterFuzz environment only supports running Android test cases ' 'on Linux host machines. Unable to reproduce the test case on ' '{current_platform}.'.format(current_platform=platform)) elif testcase.platform != platform: raise errors.ReproduceToolUnrecoverableError( 'The specified test case was discovered on {testcase_platform}. ' 'Unable to attempt to reproduce it on {current_platform}.'.format( testcase_platform=testcase.platform, current_platform=platform)) timeout = environment.get_value('TEST_TIMEOUT') result = testcase_manager.test_for_crash_with_retries( testcase, testcase_path, timeout) return result
def _prepare_environment_for_android(disable_android_setup): """Additional environment overrides needed to run on an Android device.""" environment.set_value('OS_OVERRIDE', 'ANDROID') # Bail out if we don't have an Android device connected. serial = environment.get_value('ANDROID_SERIAL') if not serial: # TODO(mbarbella): Handle the one-device case gracefully. raise errors.ReproduceToolUnrecoverableError( 'This test case requires an Android device. Please set the ' 'ANDROID_SERIAL environment variable and try again.') print('Warning: this tool will make changes to settings on the connected ' 'Android device with serial {serial} that could result in data ' 'loss.'.format(serial=serial)) willing_to_continue = prompts.get_boolean( 'Are you sure you want to continue?') if not willing_to_continue: raise errors.ReproduceToolUnrecoverableError( 'Bailing out to avoid changing settings on the connected device.') # Push the test case and build APK to the device. apk_path = environment.get_value('APP_PATH') device.update_build(apk_path, should_initialize_device=not disable_android_setup) device.push_testcases_to_device()
def _download_testcase(testcase_id, testcase, configuration): """Download the test case and return its path.""" print('Downloading testcase...') testcase_download_url = '{url}?id={id}'.format( url=configuration.get('testcase_download_url'), id=testcase_id) response, content = http_utils.request( testcase_download_url, method=http_utils.GET_METHOD, configuration=configuration) if response.status != 200: raise errors.ReproduceToolUnrecoverableError( 'Unable to download test case.') bot_absolute_filename = response['x-goog-meta-filename'] # Store the test case in the config directory for debuggability. testcase_directory = os.path.join(CONFIG_DIRECTORY, 'current-testcase') shell.remove_directory(testcase_directory, recreate=True) environment.set_value('FUZZ_INPUTS', testcase_directory) testcase_path = os.path.join(testcase_directory, os.path.basename(bot_absolute_filename)) utils.write_data_to_file(content, testcase_path) # Unpack the test case if it's archived. # TODO(mbarbella): Rewrite setup.unpack_testcase and share this code. if testcase.minimized_keys and testcase.minimized_keys != 'NA': mask = data_types.ArchiveStatus.MINIMIZED else: mask = data_types.ArchiveStatus.FUZZED if testcase.archive_state & mask: archive.unpack(testcase_path, testcase_directory) file_list = archive.get_file_list(testcase_path) testcase_path = None for file_name in file_list: if os.path.basename(file_name) == os.path.basename( testcase.absolute_path): testcase_path = os.path.join(testcase_directory, file_name) break if not testcase_path: raise errors.ReproduceToolUnrecoverableError( 'Test case file was not found in archive.\n' 'Original filename: {absolute_path}.\n' 'Archive contents: {file_list}'.format( absolute_path=testcase.absolute_path, file_list=file_list)) return testcase_path
def _reproduce_crash(testcase_url, build_directory, iterations, disable_xvfb): """Reproduce a crash.""" _prepare_initial_environment(build_directory, iterations) # Validate the test case URL and fetch the tool's configuration. testcase_id = _get_testcase_id_from_url(testcase_url) configuration = config.ReproduceToolConfiguration(testcase_url) testcase = _get_testcase(testcase_id, configuration) # Ensure that we support this test case. if testcase.platform not in SUPPORTED_PLATFORMS: raise errors.ReproduceToolUnrecoverableError( 'The reproduce tool is not yet supported on {platform}.'.format( platform=testcase.platform)) testcase_path = _download_testcase(testcase_id, testcase, configuration) _update_environment_for_testcase(testcase, build_directory) # Validate that we're running on the right platform for this test case. platform = environment.platform().lower() if testcase.platform == 'android' and platform == 'linux': _prepare_environment_for_android() elif testcase.platform == 'android' and platform != 'linux': raise errors.ReproduceToolUnrecoverableError( 'The ClusterFuzz environment only supports running Android test cases ' 'on Linux host machines. Unable to reproduce the test case on ' '{current_platform}.'.format(current_platform=platform)) elif testcase.platform != platform: raise errors.ReproduceToolUnrecoverableError( 'The specified test case was discovered on {testcase_platform}. ' 'Unable to attempt to reproduce it on {current_platform}.'.format( testcase_platform=testcase.platform, current_platform=platform)) x_processes = [] if not disable_xvfb: _setup_x() timeout = environment.get_value('TEST_TIMEOUT') print('Running testcase...') result = testcase_manager.test_for_crash_with_retries( testcase, testcase_path, timeout) # Terminate Xvfb and blackbox. for process in x_processes: process.terminate() return result
def _verify_target_exists(build_directory): """Ensure that we can find the test target before running it. Separated into its own function to simplify test behavior.""" if not build_manager.check_app_path(): raise errors.ReproduceToolUnrecoverableError( "Unable to locate app binary in {build_directory}.".format( build_directory=build_directory))
def _verify_target_exists(build_directory): """Ensure that we can find the test target before running it. Separated into its own function to simplify test behavior.""" app_path = environment.get_value('APP_PATH') if not app_path or not os.path.exists(app_path): raise errors.ReproduceToolUnrecoverableError( 'Unable to locate app binary in {build_directory}.'.format( build_directory=build_directory))
def __init__(self, testcase_url): testcase_url_parts = parse.urlparse(testcase_url) config_url = testcase_url_parts._replace( path=REPRODUCE_TOOL_CONFIG_HANDLER).geturl() response, content = http_utils.request(config_url, body={}) if response.status != 200: raise errors.ReproduceToolUnrecoverableError('Failed to access server.') self._config = json_utils.loads(content)
def _verify_app_path_exists(): """Ensure that we can find the test binary before running it. Separated into its own function to simplify test behavior.""" app_path = environment.get_value('APP_PATH', '') if not os.path.exists(app_path): raise errors.ReproduceToolUnrecoverableError( 'Unable to locate test binary at {app_path}.'.format( app_path=app_path))
def _get_testcase(testcase_id, configuration): """Retrieve the json representation of the test case with the given id.""" response, content = http_utils.request( configuration.get('testcase_info_url'), body={'testcaseId': testcase_id}, configuration=configuration) if response.status != 200: raise errors.ReproduceToolUnrecoverableError( 'Unable to fetch test case information.') testcase_map = json_utils.loads(content) return SerializedTestcase(testcase_map)
def prepare_environment(disable_android_setup): """Additional environment overrides needed to run on an Android device.""" environment.set_value('OS_OVERRIDE', 'ANDROID') # Bail out if we can't determine which Android device to use. serial = environment.get_value('ANDROID_SERIAL') if not serial: devices = get_devices() if len(devices) == 1: serial = devices[0] environment.set_value('ANDROID_SERIAL', serial) elif not devices: raise errors.ReproduceToolUnrecoverableError( 'No connected Android devices were detected. Run with the -e ' 'argument to use an emulator.') else: raise errors.ReproduceToolUnrecoverableError( 'You have multiple Android devices or emulators connected. Please ' 'set the ANDROID_SERIAL environment variable and try again.\n\n' 'Attached devices: ' + ', '.join(devices)) print('Warning: this tool will make changes to settings on the connected ' 'Android device with serial {serial} that could result in data ' 'loss.'.format(serial=serial)) willing_to_continue = prompts.get_boolean( 'Are you sure you want to continue?') if not willing_to_continue: raise errors.ReproduceToolUnrecoverableError( 'Bailing out to avoid changing settings on the connected device.') # Push the test case and build APK to the device. apk_path = environment.get_value('APP_PATH') device.update_build(apk_path, should_initialize_device=not disable_android_setup) device.push_testcases_to_device()
def _get_testcase_id_from_url(testcase_url): """Convert a testcase URL to a testcase ID.""" url_parts = parse.urlparse(testcase_url) # Testcase urls have paths like "/testcase-detail/1234567890", where the # number is the testcase ID. path_parts = url_parts.path.split("/") try: testcase_id = int(path_parts[-1]) except (ValueError, IndexError): testcase_id = 0 # Validate that the URL is correct. if (len(path_parts) != 3 or path_parts[0] or path_parts[1] != "testcase-detail" or not testcase_id): raise errors.ReproduceToolUnrecoverableError( "Invalid testcase URL {url}. Expected format: " "https://clusterfuzz-domain/testcase-detail/1234567890".format( url=testcase_url)) return testcase_id
def get_devices(): """Get a list of all connected Android devices.""" adb_runner = new_process.ProcessRunner(adb.get_adb_path()) result = adb_runner.run_and_wait(additional_args=['devices']) if result.return_code: raise errors.ReproduceToolUnrecoverableError('Unable to run adb.') # Ignore non-device lines (those before "List of devices attached"). store_devices = False devices = [] for line in result.output.splitlines(): if line == ADB_DEVICES_SEPARATOR_STRING: store_devices = True continue if not store_devices or not line: continue devices.append(line.split()[0]) return devices
def _reproduce_crash( testcase_url, build_directory, iterations, disable_xvfb, verbose, disable_android_setup, ): """Reproduce a crash.""" _prepare_initial_environment(build_directory, iterations, verbose) # Validate the test case URL and fetch the tool's configuration. testcase_id = _get_testcase_id_from_url(testcase_url) configuration = config.ReproduceToolConfiguration(testcase_url) testcase = _get_testcase(testcase_id, configuration) # For new user uploads, we'll fail without the metadata set by analyze task. if not testcase.platform: raise errors.ReproduceToolUnrecoverableError( "This test case has not yet been processed. Please try again later." ) # Ensure that we support this test case's platform. if testcase.platform not in SUPPORTED_PLATFORMS: raise errors.ReproduceToolUnrecoverableError( "The reproduce tool is not yet supported on {platform}.".format( platform=testcase.platform)) # Print warnings for this test case. if testcase.one_time_crasher_flag: print("Warning: this test case was a one-time crash. It may not be " "reproducible.") if testcase.flaky_stack: print("Warning: this test case is known to crash with different stack " "traces.") testcase_path = _download_testcase(testcase_id, testcase, configuration) _update_environment_for_testcase(testcase, build_directory) # Validate that we're running on the right platform for this test case. platform = environment.platform().lower() if testcase.platform == "android" and platform == "linux": android.prepare_environment(disable_android_setup) elif testcase.platform == "android" and platform != "linux": raise errors.ReproduceToolUnrecoverableError( "The ClusterFuzz environment only supports running Android test cases " "on Linux host machines. Unable to reproduce the test case on " "{current_platform}.".format(current_platform=platform)) elif testcase.platform != platform: raise errors.ReproduceToolUnrecoverableError( "The specified test case was discovered on {testcase_platform}. " "Unable to attempt to reproduce it on {current_platform}.".format( testcase_platform=testcase.platform, current_platform=platform)) x_processes = [] if not disable_xvfb: _setup_x() timeout = environment.get_value("TEST_TIMEOUT") print("Running testcase...") try: result = testcase_manager.test_for_crash_with_retries(testcase, testcase_path, timeout, crash_retries=1) # If we can't reproduce the crash, prompt the user to try again. if not result.is_crash(): _print_stacktrace(result) result = None use_default_retries = prompts.get_boolean( "Failed to find the desired crash on first run. Re-run " "{crash_retries} times?".format( crash_retries=environment.get_value("CRASH_RETRIES"))) if use_default_retries: print( "Attempting to reproduce test case. This may take a while..." ) result = testcase_manager.test_for_crash_with_retries( testcase, testcase_path, timeout) except KeyboardInterrupt: print("Aborting...") result = None # Terminate Xvfb and blackbox. for process in x_processes: process.terminate() return result