From 541d6f3e53158ae1a2b9ea8d5c2f8db6f9c63e3f Mon Sep 17 00:00:00 2001 From: Claus Hunsen Date: Wed, 5 Aug 2015 14:05:54 +0200 Subject: [PATCH 1/3] Update .gitignore with node.js, JetBrains and RStudio items - node.js: id_service/node_modules/ - Jetbrains (Pycharm): .idea/ - RStudio: .Rproj, .Rproj.user/ Signed-off-by: Claus Hunsen --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 6202a2bb..284936c4 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,7 @@ res/ testdat/ performance/results/ *.tar.gz +.idea/ +id_service/node_modules/ +*.Rproj +.Rproj.user/ From 3f0eba32581dee2ca957fff45377f139d6516672 Mon Sep 17 00:00:00 2001 From: Claus Hunsen Date: Fri, 21 Aug 2015 13:18:10 +0200 Subject: [PATCH 2/3] Add 'FeatureExpression' as commit dependency Until now, only the touched features have been stored as commit dependencies when using the 'feature'/'feature_file' tagging options. now, the corresponding touched feature expressions are stored additionally. - Adjust feature extraction and storage mechanisms to handle also feature expressions. - Adjust emittance of data to the DB for matching new method signatures (tuples!). Break the feature-related test suite temporarily. Signed-off-by: Claus Hunsen --- codeface/VCS.py | 103 +++++++++++++++++++++++++++--------- codeface/cluster/cluster.py | 95 +++++++++++++++++++++++---------- codeface/fileCommit.py | 9 +++- 3 files changed, 153 insertions(+), 54 deletions(-) diff --git a/codeface/VCS.py b/codeface/VCS.py index a4f569d7..abe7e8f1 100644 --- a/codeface/VCS.py +++ b/codeface/VCS.py @@ -253,8 +253,13 @@ def parse_feature_line(sep, line): """ parse the current line which is something like: FILENAME,LINE_START,LINE_END,TYPE,EXPRESSION,CONSTANTS + + Considering the #ifdef annotation "(!(defined(A)) && (!(defined(B)) && defined(C)", + the feature expression is the whole string, while the list of feature constants used + in the annotation (i.e., [A, B, C]). + :param line: the line to parse - :return: start_line, end_line, line_type, feature_list + :return: start_line, end_line, line_type, feature_list, feature_expression """ parsed_line = parse_line(sep, line) # FILENAME,LINE_START,LINE_END,TYPE,EXPRESSION,CONSTANTS @@ -272,7 +277,9 @@ def parse_feature_line(sep, line): feature_list = parsed_line[5].split(';') else: feature_list = [] - return start_line, end_line, line_type, feature_list + feature_expression = parsed_line[4] + + return start_line, end_line, line_type, feature_list, feature_expression except ValueError: raise ParseError( ("could not parse feature line (most likely because we " @@ -283,40 +290,49 @@ def parse_feature_line(sep, line): def get_feature_lines(parsed_lines, filename): """ - calculates an dictionary representing the feature sets for any line - of the given file. + calculates dictionaries representing the feature sets and the feature expressions + for any line of the given file. + + - feature expression: e.g., "(!(defined(A)) && (!(defined(B)) && defined(C)" + - feature set: e.g., [A, B, C] (all used constants in the feature expression) :param parsed_lines: a list of tuples with - (start_line, end_line, line_type, feature_list) elements + (start_line, end_line, line_type, feature_list, feature_expression) elements :param filename: the name or the analysed files (only used for descriptive error messages if the calculation fails) - :return: - feature_lines: a FileDict object to access the feature sets on any line + :return tuple of: + feature_lines: a FileDict object to access the feature sets on any line; + fexpr_lines: a FileDict object to access the feature expression on any line """ - # mapping line -> feature list, we only add changing elements + + # mapping line -> feature list|feature expression, we only add changing elements feature_lines = FileDict() feature_lines.add_line(0, []) + fexpr_lines = FileDict() + fexpr_lines.add_line(0, []) + # we want a format like (is_start, features) for every line with an # #ifdef (ie. line that changes the feature set) annotated_lines = {} + annotated_lines_fexpr = {} - def check_line(line): - if line in annotated_lines: + def check_line(line, lines_list): + if line in lines_list: raise ParseError( ("every line index can be used at most once " "(problematic line was {0} in file {1})") - .format(line, filename), filename) + .format(line, filename), filename) # We now transform the cppstats output in another output which will # help to implement the algorithm below in a simple and fast way. # The old format is a list of - # (start_line, end_line, line_type, feature_list) tuples for every - # #ifdef/#else. - # The new format is a list of (is_start, feature_set) - # for every #ifdef(/#else)/#endif + # (start_line, end_line, line_type, feature_list, feature_expression) + # tuples for every #ifdef/#else. + # The new format is a list of (is_start, feature_set) and of + # (is_start, feature_expression, line_type) for every #ifdef(/#else)/#endif. # We try to ignore #else wherever possible or handle # the #else like a nested #if. - for start_line, end_line, line_type, feature_list in parsed_lines: + for start_line, end_line, line_type, feature_list, feature_expression in parsed_lines: if not feature_list: # empty feature list is something like: '#if 0' or # '#if MACRO(0,2)', we ignore those. @@ -332,15 +348,21 @@ def check_line(line): # already be used by the start of an else/elif # (#else is the end of the previous #if # and the start of another '#if') - check_line(start_line) + check_line(start_line, annotated_lines) if end_line in annotated_lines: # in that case we just say the #else line belongs to the # virtual starting '#if' end_line -= 1 # Now end_line should be unused - check_line(end_line) + check_line(end_line, annotated_lines) annotated_lines[start_line] = (True, feature_list) annotated_lines[end_line] = (False, feature_list) + + # for same lines, the feature expression applies + check_line(start_line, annotated_lines_fexpr) + check_line(end_line, annotated_lines_fexpr) + annotated_lines_fexpr[start_line] = (True, feature_expression, line_type) + annotated_lines_fexpr[end_line] = (False, feature_expression, line_type) else: # we try to mostly ignore else and elif if the feature_ # list doesn't change @@ -366,6 +388,11 @@ def check_line(line): annotated_lines[end_line] = \ (False, old_feature_list + feature_list) + # a feature expression applies for all times as it is always different to #if branch + annotated_lines_fexpr[start_line] = (True, feature_expression, line_type) + annotated_lines_fexpr[end_line] = (False, feature_expression, line_type) + + # Now that we have calculated the annotated_lines we just calculate the # feature sets on those lines and save them in a FileDict instance. # We can always access the last feature_list with the FileDict @@ -394,7 +421,33 @@ def check_line(line): line += 1 feature_lines.add_line(line, new_feature_list) - return feature_lines + + # Convert the calculated annotated_lines_fexpr to a FileDict. + fexpr_stack = [[]] # construct stack of current feature expressions + for line in sorted(annotated_lines_fexpr): + is_start, feature_expression, line_type = annotated_lines_fexpr[line] + + if is_start: + if line_type == LineType.IF: + # start a new stack item + fexpr_stack.append([feature_expression]) + else: + # add to last stack + fexpr_stack[-1].append(feature_expression) + else: + fexpr_stack.pop() # remove last stack item + line += 1 # Remove in next line (because we want to count the current #endif line as well). + + if fexpr_stack[-1]: + # if there is an expression in the list, add this one + fexpr_lines.add_line(line, [fexpr_stack[-1][-1]]) + else: + # otherwise, add empty list + fexpr_lines.add_line(line, []) + + # return feature lines and feature-expression lines + return (feature_lines, fexpr_lines) + def get_feature_lines_from_file(file_layout_src, filename): """ @@ -440,7 +493,7 @@ def get_feature_lines_from_file(file_layout_src, filename): results_file = open(featurefile.name, 'r') sep = parse_sep_line(next(results_file)) headlines = parse_line(sep, next(results_file)) - feature_lines = \ + (feature_lines, fexpr_lines) = \ get_feature_lines( [parse_feature_line(sep, line) for line in results_file], filename) @@ -461,8 +514,10 @@ def get_feature_lines_from_file(file_layout_src, filename): empty = FileDict() empty.add_line(0, []) feature_lines = empty - # save result to the file commit instance - return feature_lines + fexpr_lines = empty + + # return resulting FileDict instances for feature sets and feature expressions + return (feature_lines, fexpr_lines) class gitVCS (VCS): def __init__(self): @@ -1239,8 +1294,8 @@ def _addBlameRev(self, rev, file_commit, blame_cmt_ids, link_type): file_commit.set_feature_infos( get_feature_lines_from_file(src_lines, file_commit.filename)) - # else: do not separate file commits into code structures, - # this will result in all commits to a single file seen as + # else: do not separate file commits into code structures, + # this will result in all commits to a single file seen as # related thus the more course grained analysis blame_cmt_ids.update( cmt_lines.values() ) diff --git a/codeface/cluster/cluster.py b/codeface/cluster/cluster.py index 6f917a8c..5f70bdd7 100755 --- a/codeface/cluster/cluster.py +++ b/codeface/cluster/cluster.py @@ -1056,7 +1056,7 @@ def createStatisticalData(cmtlist, id_mgr, link_type): return None -def writeCommitData2File(cmtlist, id_mgr, outdir, releaseRangeID, dbm, conf, +def writeCommitData2File(cmtlist, id_mgr, outdir, releaseRangeID, dbm, conf, cmt_depends=None, fileCommitDict=None): ''' commit information is written to the outdir location @@ -1191,7 +1191,7 @@ def writeIDwithCmtStats2File(id_mgr, outdir, releaseRangeID, dbm, conf): def writeDependsToDB( - logical_depends, cmtlist, dbm, conf, entity_type="Function", + logical_depends, cmtlist, dbm, conf, entity_type=("Function", ), get_entity_source_code=None): ''' Write logical dependency data to database @@ -1199,8 +1199,9 @@ def writeDependsToDB( ''' Input: - logical_depends - dictionary key=cmthash, value=list of - co-changed subroutines (i.e., functions) + logical_depends - tuple of dictionaries (key=cmthash, value=list of + co-changed subroutines (e.g., functions or features)) + entity_type - tuple of entity types corresponding to the logical_depends tuple ''' projectID = dbm.getProjectID(conf["project"], conf["tagging"]) if get_entity_source_code is None: @@ -1211,18 +1212,32 @@ def get_source(file, id): # List of tuples to store rows of DB table cmt_depend_rows = [] - for cmt in cmtlist: - if (cmt.id in logical_depends) & (logical_depends is not None): - key = dbm.getCommitId(projectID, cmt.id) - depends_list = logical_depends[cmt.id] - function_loc = [depend for depend, count in depends_list] - depend_impl_list = [' '.join(get_entity_source_code(file, funcId)) for file, funcId in function_loc] - depends_list = [depends_list[indx][0] + (depends_list[indx][1], impl) for indx, impl in enumerate(depend_impl_list)] - - # Write Function level dependencies - rows = [(key, file, entityId, entity_type, count, impl) for file, entityId, count, impl in depends_list] - cmt_depend_rows.extend(rows) - # For cmt.id + # extract commit dependencies for all entity types + for entity_type_current, logical_depends_current in zip(entity_type, logical_depends): + + # get dependencies for all commits + for cmt in cmtlist: + + # if there are dependencies for the current commit, add them to the rows to be added to DB + if (cmt.id in logical_depends_current) & (logical_depends_current is not None): + # get ID for current commit + key = dbm.getCommitId(projectID, cmt.id) + + # get the commit dependencies for current commit + depends_list = logical_depends_current[cmt.id] + function_loc = [depend for depend, count in depends_list] + + # get the corresponding source code (implementation) for all dependencies + # and add it to the current dependency item + depend_impl_list = [' '.join(get_entity_source_code(file, funcId)) + for file, funcId in function_loc] + depends_list = [depends_list[indx][0] + (depends_list[indx][1], impl) + for indx, impl in enumerate(depend_impl_list)] + + # construct rows to be put in DB + rows = [(key, file, entityId, entity_type_current, count, impl) + for file, entityId, count, impl in depends_list] + cmt_depend_rows.extend(rows) # Perform batch insert dbm.doExecCommit("INSERT INTO commit_dependency (commitId, file, entityId, entityType, size, impl)" + @@ -1323,8 +1338,8 @@ def get_links_received_by_id_max_group_name(id_receiver, id_sender): out.close() -def emitStatisticalData(cmtlist, id_mgr, logical_depends, outdir, releaseRangeID, dbm, conf, - fileCommitDict, entity_type="Function", get_entity_source_code=None): +def emitStatisticalData(cmtlist, id_mgr, logical_depends, outdir, releaseRangeID, dbm, conf, + fileCommitDict, entity_type=("Function", ), get_entity_source_code=None): """Save the available information for a release interval for further statistical processing. Several files are created in outdir respectively the database: @@ -1440,19 +1455,24 @@ def compute_logical_depends_features(file_commit_list, cmt_dict, start_date): contains the source code structural information and a commit reference for every line of a file Output: - feature_depends - dictionary where key=commit hash (unique id) and - value=the list of features changed with that commit + feature_depends - dictionaries where key=commit hash (unique id) and + value=the features or feature expressions changed with that commit Description: We use the source code structural information we acquired from the cppstats analysis to identify which lines of code fall under a particular feature space. We save the subroutine name together with the filename that the subroutine belongs. + + - feature expression: e.g., "(!(defined(A)) && (!(defined(B)) && defined(C)" + - feature set: e.g., [A, B, C] (all used constants in feature expression) ''' feature_depends_count = {} + fexpr_depends_count = {} for file in file_commit_list.values(): feature_depends = {} + fexpr_depends = {} filename = file.getFilename() idx = file.getIndx() for line_num in idx: @@ -1461,10 +1481,14 @@ def compute_logical_depends_features(file_commit_list, cmt_dict, start_date): if cmt_id not in feature_depends_count: feature_depends_count[cmt_id] = [] + if cmt_id not in fexpr_depends_count: + fexpr_depends_count[cmt_id] = [] + if cmt_id in cmt_dict: # If line is older than start date then ignore if cmt_dict[cmt_id].getCdate() >= start_date: feature_list = file.findFeatureList(line_num) + feature_expression_list = file.findFeatureExpression(line_num) feature_loc = [(filename, feature) for feature in feature_list] if cmt_id in feature_depends: @@ -1472,6 +1496,12 @@ def compute_logical_depends_features(file_commit_list, cmt_dict, start_date): else: feature_depends[cmt_id] = feature_loc + fexpr_loc = [(filename, fexpr) for fexpr in feature_expression_list] + if cmt_id in fexpr_depends: + fexpr_depends[cmt_id].extend(fexpr_loc) + else: + fexpr_depends[cmt_id] = fexpr_loc + # Compute the number of lines of code changed for each dependency. # We captured the function dependency on a line by line basis above # now we aggregate the lines that change one function @@ -1480,7 +1510,15 @@ def compute_logical_depends_features(file_commit_list, cmt_dict, start_date): [(feature_id, len(list(group))) for feature_id, group in itertools.groupby(sorted(depend_list))]) - return feature_depends_count + # Same for feature expressions + for cmt_id, depend_list in fexpr_depends.iteritems(): + fexpr_depends_count[cmt_id].extend( + [(feature_id, len(list(group))) + for feature_id, group in itertools.groupby(sorted(depend_list))] + ) + + + return (feature_depends_count, fexpr_depends_count) def computeProximityLinks(fileCommitList, cmtList, id_mgr, link_type, \ @@ -1813,8 +1851,8 @@ def performAnalysis(conf, dbm, dbfilename, git_repo, revrange, subsys_descr, if subsys_descr != None: id_mgr.setSubsysNames(subsys_descr.keys()) - logical_depends = None - entity_type = "Function" + logical_depends = (None) + entity_type = ("Function", ) get_entity_source_code = None fileCommitDict = git.getFileCommitDict() #--------------------------------- @@ -1835,13 +1873,14 @@ def performAnalysis(conf, dbm, dbfilename, git_repo, revrange, subsys_descr, if link_type in (LinkType.proximity, LinkType.file): computeProximityLinks( fileCommitDict, cmtdict, id_mgr, link_type, startDate) - logical_depends = computeLogicalDepends( - fileCommitDict, cmtdict, startDate) + # for the current functions, we need a tuple here + logical_depends = (computeLogicalDepends( + fileCommitDict, cmtdict, startDate), ) def get_source(file, func_id): return fileCommitDict[file].getFuncImpl(func_id) get_entity_source_code = get_source - entity_type = "Function" + entity_type = ("Function", ) elif link_type == LinkType.feature_file: compute_feature_proximity_links_per_file( fileCommitDict, cmtdict, id_mgr, link_type, startDate) @@ -1851,7 +1890,7 @@ def get_source(file, func_id): def get_source(file, feature_id): return "" get_entity_source_code = get_source - entity_type = "Feature" + entity_type = ("Feature", "FeatureExpression") elif link_type == LinkType.feature: compute_feature_proximity_links( fileCommitDict, cmtdict, id_mgr, link_type, startDate) @@ -1861,7 +1900,7 @@ def get_source(file, feature_id): def get_source(file, feature_id): return "" get_entity_source_code = get_source - entity_type = "Feature" + entity_type = ("Feature", "FeatureExpression") #--------------------------------- #compute statistical information diff --git a/codeface/fileCommit.py b/codeface/fileCommit.py index 6857a782..c6b530a8 100644 --- a/codeface/fileCommit.py +++ b/codeface/fileCommit.py @@ -119,8 +119,9 @@ def __init__(self): # meta data self._src_elem_list = [] - # dictionary with key = line number, value = feature list + # dictionaries with key = line number, value = feature list|feature expression self.feature_info = FileDict() + self.feature_expression_info = FileDict() #Getter/Setters def getFileSnapShots(self): @@ -154,7 +155,8 @@ def setSrcElems(self, src_elem_list): self._src_elem_list.extend(src_elem_list) def set_feature_infos(self, feature_line_infos): - self.feature_info = feature_line_infos + self.feature_info = feature_line_infos[0] + self.feature_expression_info = feature_line_infos[1] #Methods def addFileSnapShot(self, key, dict): @@ -191,3 +193,6 @@ def addFuncImplLine(self, lineNum, srcLine): def findFeatureList(self, line_index): return self.feature_info.get_line_info(int(line_index) + 1) + + def findFeatureExpression(self, line_index): + return self.feature_expression_info.get_line_info(int(line_index) + 1) From 3260097a0cc417518711695d6dd90afa682f2cf1 Mon Sep 17 00:00:00 2001 From: Claus Hunsen Date: Fri, 21 Aug 2015 13:14:25 +0200 Subject: [PATCH 3/3] Fix feature-related tests As the addtion of the feature-expression to the commit dependencies broke the test suite, the tests needed a fix. Get the tests going green again. Signed-off-by: Claus Hunsen --- codeface/test/integration/test_features.py | 29 ++++- codeface/test/unit/test_cppstats_works.py | 13 ++- codeface/test/unit/test_getFeatureLines.py | 123 ++++++++++++++++----- 3 files changed, 133 insertions(+), 32 deletions(-) diff --git a/codeface/test/integration/test_features.py b/codeface/test/integration/test_features.py index fb78393d..ffd05108 100644 --- a/codeface/test/integration/test_features.py +++ b/codeface/test/integration/test_features.py @@ -208,16 +208,22 @@ class TestEndToEndOnlyTaggingExample3Feature( 'A', 'Feature', 1, None), ('f95b8047236f75641d6d7a2b5790b9e1db869ccd', 'src/carp.c', 'B', 'Feature', 1, None), + ('f95b8047236f75641d6d7a2b5790b9e1db869ccd', 'src/carp.c', + '(defined(A) || defined(B))', 'FeatureExpression', 1, None), ('7b16cf10845bc64e2589fa63822f3ddc49aedd4d', 'src/carp.c', 'A', 'Feature', 1, None), ('7b16cf10845bc64e2589fa63822f3ddc49aedd4d', 'src/carp.c', 'B', 'Feature', 1, None), + ('7b16cf10845bc64e2589fa63822f3ddc49aedd4d', 'src/carp.c', + '(defined(A) || defined(B))', 'FeatureExpression', 1, None), ('3fe9884f98487cce4603d2bd5578e94944412d3c', 'src/carp.c', 'A', 'Feature', 1, None), ('3fe9884f98487cce4603d2bd5578e94944412d3c', 'src/carp.c', 'B', 'Feature', 1, None), + ('3fe9884f98487cce4603d2bd5578e94944412d3c', 'src/carp.c', + '(defined(A) || defined(B))', 'FeatureExpression', 1, None), # Release 2 (see blame data above) ('c9b59046b6eb473b97a97cb31aded2deced29dc6', 'src/code.c', @@ -226,20 +232,37 @@ class TestEndToEndOnlyTaggingExample3Feature( 'B', 'Feature', 3, None), ('c9b59046b6eb473b97a97cb31aded2deced29dc6', 'src/code.c', 'C', 'Feature', 2, None), + ('c9b59046b6eb473b97a97cb31aded2deced29dc6', 'src/code.c', + '(defined(C))', 'FeatureExpression', 2, None), + ('c9b59046b6eb473b97a97cb31aded2deced29dc6', 'src/code.c', + '(defined(A))', 'FeatureExpression', 1, None), + ('c9b59046b6eb473b97a97cb31aded2deced29dc6', 'src/code.c', + '(!((defined(A)))) && ((defined(B)))', 'FeatureExpression', 1, None), + ('c9b59046b6eb473b97a97cb31aded2deced29dc6', 'src/code.c', + '(!((defined(A)))) && (!((defined(B))))', 'FeatureExpression', 2, None), ('29b9c8bc6955df51263201dff7a1d935f8cd6049', 'src/code.c', 'C', 'Feature', 1, None), + ('29b9c8bc6955df51263201dff7a1d935f8cd6049', 'src/code.c', + '(defined(C))', 'FeatureExpression', 1, None), ('c52343ac0d17ce9a30866d296da0deb23f1567a7', 'src/code.c', 'A', 'Feature', 3, None), ('c52343ac0d17ce9a30866d296da0deb23f1567a7', 'src/code.c', 'B', 'Feature', 2, None), - + ('c52343ac0d17ce9a30866d296da0deb23f1567a7', 'src/code.c', + '(defined(A))', 'FeatureExpression', 1, None), + ('c52343ac0d17ce9a30866d296da0deb23f1567a7', 'src/code.c', + '(!((defined(A)))) && ((defined(B)))', 'FeatureExpression', 1, None), + ('c52343ac0d17ce9a30866d296da0deb23f1567a7', 'src/code.c', + '(!((defined(A)))) && (!((defined(B))))', 'FeatureExpression', 1, None), ('55eec10019857e44d80e4bec3e81d1cffb785592', 'src/carp.c', 'A', 'Feature', 1, None), ('55eec10019857e44d80e4bec3e81d1cffb785592', 'src/carp.c', - 'B', 'Feature', 1, None) + 'B', 'Feature', 1, None), + ('55eec10019857e44d80e4bec3e81d1cffb785592', 'src/carp.c', + '(defined(A) || defined(B))', 'FeatureExpression', 1, None) ] @@ -285,4 +308,4 @@ class TestEndToEndOnlyTaggingExample3Feature_File( # example_project = 2 # tagging = "tag" # correct_edges = None -# #testEndToEnd = unittest.expectedFailure(TestEndToEndOnlyTagging.testEndToEnd) \ No newline at end of file +# #testEndToEnd = unittest.expectedFailure(TestEndToEndOnlyTagging.testEndToEnd) diff --git a/codeface/test/unit/test_cppstats_works.py b/codeface/test/unit/test_cppstats_works.py index edd027e0..c401c243 100644 --- a/codeface/test/unit/test_cppstats_works.py +++ b/codeface/test/unit/test_cppstats_works.py @@ -67,7 +67,7 @@ def test_simple_analysis(self): #endif """ d = self._get_file_layout(file) - feature_dict = get_feature_lines_from_file(d, "unittest.c") + feature_dict, fexpr_lines = get_feature_lines_from_file(d, "unittest.c") self.assertSetEqual(feature_dict.get_line_info(1), set([])) self.assertSetEqual(feature_dict.get_line_info(2), set(["Test"])) @@ -76,4 +76,13 @@ def test_simple_analysis(self): self.assertSetEqual(feature_dict.get_line_info(5), set(["Test"])) self.assertSetEqual(feature_dict.get_line_info(6), set(["Test"])) self.assertSetEqual(feature_dict.get_line_info(7), set([])) - pass \ No newline at end of file + + self.assertSetEqual(fexpr_lines.get_line_info(1), set([])) + self.assertSetEqual(fexpr_lines.get_line_info(2), set(["Test"])) + self.assertSetEqual(fexpr_lines.get_line_info(3), set(["Test"])) + self.assertSetEqual(fexpr_lines.get_line_info(4), set(["!(Test)"])) + self.assertSetEqual(fexpr_lines.get_line_info(5), set(["!(Test)"])) + self.assertSetEqual(fexpr_lines.get_line_info(6), set(["!(Test)"])) + self.assertSetEqual(fexpr_lines.get_line_info(7), set([])) + + pass diff --git a/codeface/test/unit/test_getFeatureLines.py b/codeface/test/unit/test_getFeatureLines.py index 4d189676..3de29ed5 100644 --- a/codeface/test/unit/test_getFeatureLines.py +++ b/codeface/test/unit/test_getFeatureLines.py @@ -73,33 +73,37 @@ def testline(self): def testfeatureline(self): """Check that we can parse the first header line""" - startline, endline, line_type, featurelist = \ + startline, endline, line_type, featurelist, feature_expression = \ parse_feature_line(",", "/tmp/tmpVemX4s_cppstats_featurelocations/_cppstats_featurelocations/tmpuAFx3b.xml,3,5,#if,(defined(A)) && ((defined(C) || defined(D))),A;C;D") self.assertEqual(3, startline) self.assertEqual(5, endline) self.assertEqual(LineType.IF, line_type) self.assertListEqual(["A", "C", "D"], featurelist) + self.assertEqual("(defined(A)) && ((defined(C) || defined(D)))", feature_expression) - startline, endline, line_type, featurelist = \ + startline, endline, line_type, featurelist, feature_expression = \ parse_feature_line(",", "/tmp/tmpVemX4s_cppstats_featurelocations/_cppstats_featurelocations/tmpuAFx3b.xml,1,8,#if,defined(A),A") self.assertEqual(1, startline) self.assertEqual(8, endline) self.assertEqual(LineType.IF, line_type) self.assertListEqual(["A"], featurelist) + self.assertEqual("defined(A)", feature_expression) - startline, endline, line_type, featurelist = \ + startline, endline, line_type, featurelist, feature_expression = \ parse_feature_line(",", "/tmp/tmpbPbqDy_cppstats_featurelocations/_cppstats_featurelocations/tmp5pTBQ4.xml,324,335,#if,0,") self.assertEqual(324, startline) self.assertEqual(335, endline) self.assertEqual(LineType.IF, line_type) self.assertListEqual([], featurelist) + self.assertEqual("0", feature_expression) - startline, endline, line_type, featurelist = \ + startline, endline, line_type, featurelist, feature_expression = \ parse_feature_line(",", "/tmp/tmpY5XZci_cppstats_featurelocations/_cppstats_featurelocations/tmpWwrMnP.xml,941,943,#else,\"!(GTK_CHECK_VERSION(3, 0, 0))\",") self.assertEqual(941, startline) self.assertEqual(943, endline) self.assertEqual(LineType.ELSE, line_type) self.assertListEqual([], featurelist) + self.assertEqual("!(GTK_CHECK_VERSION(3, 0, 0))", feature_expression) pass @@ -109,21 +113,29 @@ class TestFeatureLines(unittest.TestCase): def testsingleline(self): """Check that a single line is split as expected""" - feature_dict = get_feature_lines([(3, 5, LineType.IF, ["A", "B"])], - "unittest.c") + feature_dict, fexpr_dict = \ + get_feature_lines([(3, 5, LineType.IF, ["A", "B"], "defined(A) && defined(B)")], + "unittest.c") self.assertSetEqual(feature_dict.get_line_info(2), set([])) self.assertSetEqual(feature_dict.get_line_info(3), set(["A", "B"])) self.assertSetEqual(feature_dict.get_line_info(4), set(["A", "B"])) self.assertSetEqual(feature_dict.get_line_info(5), set(["A", "B"])) self.assertSetEqual(feature_dict.get_line_info(6), set([])) + + self.assertSetEqual(fexpr_dict.get_line_info(2), set([])) + self.assertSetEqual(fexpr_dict.get_line_info(3), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(4), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(5), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(6), set([])) + pass def testfolllowingline(self): """Check that a #ifdef can follow another #ifdef""" - feature_dict = \ + feature_dict, fexpr_dict = \ get_feature_lines( - [(3, 5, LineType.IF, ["A", "B"]), - (6, 8, LineType.IF, ["C", "D"])], + [(3, 5, LineType.IF, ["A", "B"], "defined(A) && defined(B)"), + (6, 8, LineType.IF, ["C", "D"], "defined(C) && defined(D)")], "unittest.c") self.assertSetEqual(feature_dict.get_line_info(2), set([])) self.assertSetEqual(feature_dict.get_line_info(3), set(["A", "B"])) @@ -133,14 +145,23 @@ def testfolllowingline(self): self.assertSetEqual(feature_dict.get_line_info(7), set(["C", "D"])) self.assertSetEqual(feature_dict.get_line_info(8), set(["C", "D"])) self.assertSetEqual(feature_dict.get_line_info(9), set([])) + + self.assertSetEqual(fexpr_dict.get_line_info(2), set([])) + self.assertSetEqual(fexpr_dict.get_line_info(3), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(4), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(5), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(6), set(["defined(C) && defined(D)"])) + self.assertSetEqual(fexpr_dict.get_line_info(7), set(["defined(C) && defined(D)"])) + self.assertSetEqual(fexpr_dict.get_line_info(8), set(["defined(C) && defined(D)"])) + self.assertSetEqual(fexpr_dict.get_line_info(9), set([])) pass def testorderdoesntmatter(self): """Check that a #ifdef can follow another #ifdef""" - feature_dict = \ + feature_dict, fexpr_dict = \ get_feature_lines( - [(6, 8, LineType.IF, ["C", "D"]), - (3, 5, LineType.IF, ["A", "B"])], + [(6, 8, LineType.IF, ["C", "D"], "defined(C) && defined(D)"), + (3, 5, LineType.IF, ["A", "B"], "defined(A) && defined(B)")], "unittest.c") self.assertSetEqual(feature_dict.get_line_info(2), set([])) self.assertSetEqual(feature_dict.get_line_info(3), set(["A", "B"])) @@ -150,14 +171,25 @@ def testorderdoesntmatter(self): self.assertSetEqual(feature_dict.get_line_info(7), set(["C", "D"])) self.assertSetEqual(feature_dict.get_line_info(8), set(["C", "D"])) self.assertSetEqual(feature_dict.get_line_info(9), set([])) + + self.assertSetEqual(fexpr_dict.get_line_info(2), set([])) + self.assertSetEqual(fexpr_dict.get_line_info(3), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(4), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(5), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(6), set(["defined(C) && defined(D)"])) + self.assertSetEqual(fexpr_dict.get_line_info(7), set(["defined(C) && defined(D)"])) + self.assertSetEqual(fexpr_dict.get_line_info(8), set(["defined(C) && defined(D)"])) + self.assertSetEqual(fexpr_dict.get_line_info(9), set([])) + pass def testnesting(self): """Check that a #ifdef can be nested in an another #ifdef""" - feature_dict = \ + feature_dict, fexpr_dict = \ get_feature_lines( - [(3, 9, LineType.IF, ["A", "B"]), - (6, 8, LineType.IF, ["C", "D"])], + [(3, 9, LineType.IF, ["A", "B"], "defined(A) && defined(B)"), + (6, 8, LineType.IF, ["C", "D"], + "(defined(A) && defined(B)) && (defined(C) && defined(D))")], "unittest.c") self.assertSetEqual(feature_dict.get_line_info(2), set([])) self.assertSetEqual(feature_dict.get_line_info(3), set(["A", "B"])) @@ -171,15 +203,29 @@ def testnesting(self): set(["A", "B", "C", "D"])) self.assertSetEqual(feature_dict.get_line_info(9), set(["A", "B"])) self.assertSetEqual(feature_dict.get_line_info(10), set([])) + + self.assertSetEqual(fexpr_dict.get_line_info(2), set([])) + self.assertSetEqual(fexpr_dict.get_line_info(3), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(4), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(5), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(6), + set(["(defined(A) && defined(B)) && (defined(C) && defined(D))"])) + self.assertSetEqual(fexpr_dict.get_line_info(7), + set(["(defined(A) && defined(B)) && (defined(C) && defined(D))"])) + self.assertSetEqual(fexpr_dict.get_line_info(8), + set(["(defined(A) && defined(B)) && (defined(C) && defined(D))"])) + self.assertSetEqual(fexpr_dict.get_line_info(9), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(10), set([])) pass def testnestingwithsamefeatures(self): """Check that a #ifdef can be nested in another #ifdef but have the same feature""" - feature_dict = \ + feature_dict, fexpr_dict = \ get_feature_lines( - [(3, 9, LineType.IF, ["A", "B"]), - (6, 8, LineType.IF, ["A", "D"])], + [(3, 9, LineType.IF, ["A", "B"], "defined(A) && defined(B)"), + (6, 8, LineType.IF, ["A", "D"], + "(defined(A) && defined(B)) && (defined(D))")], "unittest.c") self.assertSetEqual(feature_dict.get_line_info(2), set([]), "line 2 should be empty") @@ -194,27 +240,41 @@ def testnestingwithsamefeatures(self): set(["A", "B", "D"])) self.assertSetEqual(feature_dict.get_line_info(9), set(["A", "B"])) self.assertSetEqual(feature_dict.get_line_info(10), set([])) + + self.assertSetEqual(fexpr_dict.get_line_info(2), set([]), + "line 2 should be empty") + self.assertSetEqual(fexpr_dict.get_line_info(3), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(4), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(5), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(6), + set(["(defined(A) && defined(B)) && (defined(D))"])) + self.assertSetEqual(fexpr_dict.get_line_info(7), + set(["(defined(A) && defined(B)) && (defined(D))"])) + self.assertSetEqual(fexpr_dict.get_line_info(8), + set(["(defined(A) && defined(B)) && (defined(D))"])) + self.assertSetEqual(fexpr_dict.get_line_info(9), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(10), set([])) pass def testinvalidstartend(self): """Check we throw when end is before start""" self.assertRaises(ParseError, get_feature_lines, - [(5, 3, LineType.IF, ["A", "B"])], "unittest.c") + [(5, 3, LineType.IF, ["A", "B"], "defined(A) && defined(B)")], "unittest.c") pass def testoverlapping(self): """Check we throw when line is used multiple times""" self.assertRaises(ParseError, get_feature_lines, - [(3, 5, LineType.IF, ["A", "B"]), - (3, 6, LineType.IF, ["C"])], + [(3, 5, LineType.IF, ["A", "B"], "defined(A) && defined(B)"), + (3, 6, LineType.IF, ["C"], "defined(C)")], "unittest.c") pass def testoverlapping_2(self): """Check we throw when line is used multiple times""" self.assertRaises(ParseError, get_feature_lines, - [(3, 5, LineType.IF, ["A", "B"]), - (5, 6, LineType.IF, ["C"])], + [(3, 5, LineType.IF, ["A", "B"], "defined(A) && defined(B)"), + (5, 6, LineType.IF, ["C"], "defined(C)")], "unittest.c") pass @@ -222,10 +282,10 @@ def testoverlapping_2(self): def testelif(self): """Check we throw when line is used multiple times""" # for example #elif "C" on line 5 - feature_dict = \ + feature_dict, fexpr_dict = \ get_feature_lines( - [(3, 5, LineType.IF, ["A", "B"]), - (5, 6, LineType.ELIF, ["A", "B", "C"])], + [(3, 5, LineType.IF, ["A", "B"], "defined(A) && defined(B)"), + (5, 6, LineType.ELIF, ["A", "B", "C"], "(!(defined(A)) && (!(defined(B)) && defined(C)")], "unittest.c") self.assertSetEqual(feature_dict.get_line_info(2), set([])) self.assertSetEqual(feature_dict.get_line_info(3), set(["A", "B"])) @@ -235,4 +295,13 @@ def testelif(self): self.assertSetEqual(feature_dict.get_line_info(6), set(["A", "B", "C"])) self.assertSetEqual(feature_dict.get_line_info(7), set([])) - pass \ No newline at end of file + + self.assertSetEqual(fexpr_dict.get_line_info(2), set([])) + self.assertSetEqual(fexpr_dict.get_line_info(3), set(["defined(A) && defined(B)"]), fexpr_dict.get_line_info(3)) + self.assertSetEqual(fexpr_dict.get_line_info(4), set(["defined(A) && defined(B)"])) + self.assertSetEqual(fexpr_dict.get_line_info(5), + set(["(!(defined(A)) && (!(defined(B)) && defined(C)"])) + self.assertSetEqual(fexpr_dict.get_line_info(6), + set(["(!(defined(A)) && (!(defined(B)) && defined(C)"])) + self.assertSetEqual(fexpr_dict.get_line_info(7), set([])) + pass