Пример #1
0
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
Пример #2
0
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()
Пример #3
0
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
Пример #4
0
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
Пример #5
0
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))
Пример #6
0
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))
Пример #7
0
  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)
Пример #8
0
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))
Пример #9
0
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)
Пример #10
0
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()
Пример #11
0
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
Пример #12
0
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
Пример #13
0
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