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
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()
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
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
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)
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
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))