예제 #1
0
def snapshot(repository: Repository,
             commit_type=DEVELOPER_CHANGE,
             allow_empty=False,
             commit_only=False):
    '''
    Snapshots a dirty commit tree and records everything
    '''
    commit = None
    try:
        session = Session.object_session(repository)

        commit = create_commit(repository,
                               commit_type=commit_type,
                               allow_empty=allow_empty)

        if not commit_only:
            commit = snapshot_commit(repository, commit)

        git_push(repository)

        session.commit()
        return commit

    except Exception as e:
        traceback.print_exc()
        import pdb
        pdb.set_trace()
        # revert all the local edits
        logger.error('Hit the following exception: {}'.format(e))

        if commit:
            logger.error('Reverting local changes')
            revert_commit(repository, commit)

        raise e
예제 #2
0
def _apply_diff(diff: Diff, revert=False):
    '''
    Either reverts or applies a diff
    '''
    temp_output = None
    try:
        file_name = 'bugbuddy_diff_id_{}'.format(diff.id)
        suffix = '.patch'
        temp_output = tempfile.NamedTemporaryFile(
            prefix=file_name, suffix=suffix, dir=diff.commit.repository.path)
        temp_output.write(str.encode(diff.patch + '\n\n'))
        temp_output.flush()

        command = ('git apply {revert}{file_path}'.format(
            revert='-R ' if revert else '', file_path=temp_output.name))

        stdout, stderr = run_cmd(diff.commit.repository, command)
        if stderr:
            msg = (
                'Error trying to {revert_or_add} diff {diff} with patch:\n{patch}\n\n'
                'stderr: {stderr}'.format(
                    revert_or_add='revert' if revert else 'add',
                    diff=diff,
                    patch=diff.patch,
                    stderr=stderr))
            raise BugBuddyError(msg)
    except Exception as e:
        import pdb
        pdb.set_trace()
        logger.error('Hit error trying to apply diff: {}'.format(e))
    finally:
        if temp_output:
            temp_output.close()
예제 #3
0
def get_range_of_patch(patch):
    '''
    Given a whatthepatch patch, it will return the start and end range of the
    patch.  We consider the start of the patch not to be the first line
    necessarily, but the first line that is changed.
    '''
    start_range = None
    end_range = None

    for original_line, new_line, change in patch.changes:
        if original_line is None or new_line is None:
            if not start_range:
                start_range = new_line or original_line

            if not end_range or (new_line or -1) > end_range:
                end_range = new_line or original_line

    if start_range is None or end_range is None:
        import pdb
        pdb.set_trace()
        logger.error('Failed to get start_range or end_range')

    logger.info('{}-{}'.format(start_range, end_range))
    # if end_range - start_range > 25 or (start_range == 0 and end_range == 132):
    #     import pdb; pdb.set_trace()
    #     logger.error('What in tarnation')

    return start_range, end_range
예제 #4
0
def create_diffs(repository: Repository,
                 commit: Commit = None,
                 is_synthetic=False,
                 function: Function = None,
                 allow_empty=True,
                 only_unstaged=False) -> DiffList:
    '''
    Returns a list of diffs from a repository
    '''
    session = Session.object_session(repository)
    if not commit:
        commit = get_most_recent_commit(repository)

    diffs = []

    # the patches should be split on a per function basis
    patches = get_diff_patches(commit, only_unstaged=only_unstaged)

    if not allow_empty and not patches:
        import pdb
        pdb.set_trace()
        logger.error('No diffs discovered when allow_no_diffs == False')
        get_diff_patches(commit)

    for patch in patches:
        diff_function = function
        file_path = patch.header.new_path
        first_line, last_line = get_range_of_patch(patch)

        if not diff_function:
            function_history = commit.get_corresponding_function(
                file_path=file_path,
                start_range=first_line,
                end_range=last_line,
            )
            diff_function = function_history.function if function_history else None

        diff = create(session,
                      Diff,
                      commit=commit,
                      patch=patch.text,
                      function=diff_function,
                      file_path=file_path,
                      is_synthetic=is_synthetic,
                      first_line=first_line,
                      last_line=last_line)

        diffs.append(diff)

    return diffs
예제 #5
0
def save_function_histories(repository: Repository, commit: Commit,
                            function_nodes, patches):
    '''
    Stores the function histories
    '''
    session = Session.object_session(repository)

    previous_commit = get_previous_commit(commit)

    if not previous_commit:
        logger.info('No previous commit, creating new function nodes')
        create_new_functions_from_nodes(commit, function_nodes)
        return

    # from the patches, determine which files were altered
    altered_files = list(set([patch.header.new_path for patch in patches]))
    logger.info('Altered files: {}'.format(altered_files))

    altered_function_nodes = []
    unaltered_function_nodes = []
    for function_node in function_nodes:
        if function_node.file_path in altered_files:
            altered_function_nodes.append(function_node)
        else:
            unaltered_function_nodes.append(function_node)

    # If the file was not altered, then we can simply find the previous
    # function history and recreate it for this commit without even finding
    # the appropriate function
    for function_node in unaltered_function_nodes:
        try:
            previous_function_history = (session.query(FunctionHistory).join(
                FunctionHistory.function
            ).filter(FunctionHistory.commit_id == previous_commit.id).filter(
                Function.name == function_node.name).filter(
                    Function.file_path == function_node.file_path).filter(
                        FunctionHistory.first_line == function_node.lineno)
                                         ).one()
        except NoResultFound:
            close_no_cigar = (session.query(FunctionHistory).join(
                FunctionHistory.function).filter(
                    FunctionHistory.commit_id == previous_commit.id).filter(
                        Function.name == function_node.name).filter(
                            Function.file_path == function_node.file_path)
                              ).all()
            import pdb
            pdb.set_trace()
            logger.error('Unable to find previous function history for node {}'
                         'which was in an unaltered file')

        function_history = create(
            session,
            FunctionHistory,
            function=previous_function_history.function,
            commit=commit,
            node=function_node,
            first_line=previous_function_history.first_line,
            last_line=previous_function_history.last_line)

        # logger.info('Created unaltered function history: {}'
        #             .format(function_history))

    # If the file was altered, then we need to be extremely careful about how
    # we track function history.
    _save_altered_file_function_history(commit, previous_commit, altered_files,
                                        altered_function_nodes, patches)
예제 #6
0
def generate_synthetic_test_results(repository: Repository, run_limit: int):
    '''
    Creates multiple synthetic changes and test results
    '''
    session = Session.object_session(repository)
    synthetic_diffs = repository.get_synthetic_diffs()

    if not synthetic_diffs:
        # create the synthetic diffs
        create_synthetic_alterations(repository)
        logger.info('You have created the base synthetic commits.  Congrats!')
        session.commit()
        synthetic_diffs = repository.get_synthetic_diffs()

    num_runs = 0
    for diff_set in yield_blame_set(synthetic_diffs):
        logger.info('On diff set: {}'.format(diff_set))

        for diff_subset in powerset(diff_set):
            logger.info('On run #{} with: {}'.format(num_runs, diff_subset))
            db_and_git_match(repository)

            try:
                # see if we already have a commit and test run for the diff set.
                # if we do, continue
                logger.debug('1: {}'.format(time.time()))
                commit = get_matching_commit_for_diffs(repository, diff_subset)

                # if the commit does not already exist for this set, then we
                # need to create it and run tests against it
                if not commit:
                    # revert back to a clean repository
                    reset_commit = create_reset_commit(repository)
                    if reset_commit:
                        logger.info('Storing reset commit')
                        snapshot_commit(repository, reset_commit)
                        git_push(repository)
                        session.commit()
                        db_and_git_match(repository)

                    # create a commit.  Only allow an empty commit if there
                    # nothing in the diff
                    commit = create_commit(repository,
                                           name=SYNTHETIC_CHANGE,
                                           commit_type=SYNTHETIC_CHANGE,
                                           allow_empty=True)

                    logger.info('Applying diffs')
                    # apply the synthetic diffs to the mirrored repository
                    apply_synthetic_diffs(commit, diff_subset)

                    # store the rest of the commit data.  No need to recreate
                    # the diffs since they have already been stored in
                    # apply_synthetic_diffs
                    logger.info('Snapshotting the commit: {}'.format(commit))
                    commit = snapshot_commit(repository,
                                             commit,
                                             skip_diffs=True)

                # add the commit hash id for its synthetic diffs
                logger.info('Creating synthetic diff hash')
                if not commit.synthetic_diff_hash:
                    base_synthetic_ids = [diff.id for diff in diff_subset]
                    commit.synthetic_diff_hash = (
                        get_hash_given_base_synthetic_ids(base_synthetic_ids))
                    logger.info('Added hash_ids #{} to commit: {}'.format(
                        commit.synthetic_diff_hash, commit))

                logger.info('Running tests')
                if not commit.test_runs:
                    # run all tests against the synthetic change
                    run_all_tests(commit)

                logger.debug('2: {}'.format(time.time()))
                if commit.needs_blaming():
                    synthetic_blame(commit, commit.test_runs[0])

                logger.debug('3: {}'.format(time.time()))
                session.commit()

                # push newly created commit
                git_push(repository)

                logger.info('Completed run #{}'.format(num_runs))

                num_runs += 1
                if run_limit and num_runs >= run_limit:
                    logger.info('Completed all #{} runs! '.format(num_runs))
                    exit()

            except Exception as e:
                # revert all the local edits
                logger.error('Hit the following exception: {}'.format(e))
                logger.error('Reverting local changes')
                revert_to_master(repository)
                raise e
예제 #7
0
def create_results_from_junit_xml(output_file: str, repository: Repository,
                                  test_run: TestRun):
    '''
    Gets results from a JUnitXML format file
    https://docs.pytest.org/en/latest/usage.html#creating-junitxml-format-files
    '''
    logger.info('Reading info from {}'.format(output_file))
    try:
        session = Session.object_session(test_run)
        xml_output = JUnitXml.fromfile(output_file)

        test_names = []

        expected_new = False

        for test_case in xml_output:
            # There can seemingly be duplicate test outputs for a test if both
            # the test and the test's teardown step both fail.  So we will ignore
            # the second test output
            unique_id = '{}/{}/{}'.format(
                test_case.name, test_case._elem.attrib.get('file'),
                test_case._elem.attrib.get('classname'))
            if unique_id in test_names:
                logger.error(
                    'There was a duplicate test output for test: {}'.format(
                        test_case.name))
                continue

            test_names.append(unique_id)

            test, is_new = get_or_create(
                session,
                Test,
                repository=repository,
                name=test_case.name,
                file=test_case._elem.attrib.get('file'),
                classname=test_case._elem.attrib.get('classname'),
            )

            if is_new and not expected_new:
                import pdb
                pdb.set_trace()
                logger.error('Did you expect to create a new test?')
                expected_new = True

            status = TEST_OUTPUT_FAILURE if test_case.result else TEST_OUTPUT_SUCCESS

            # if the test is skipped, do not keep it
            if hasattr(test_case, 'result') and hasattr(
                    test_case.result, 'type'):
                if test_case.result.type == 'pytest.skip':
                    status = TEST_OUTPUT_SKIPPED

            create(
                session,
                TestResult,
                test=test,
                test_run=test_run,
                status=status,
                time=test_case.time,
            )

    except Exception as e:
        import pdb
        pdb.set_trace()
        logger.info('Hit error when reading from junit xml: {}'.format(e))