[codeface] Re: [PATCH] added 'FeatureExpression' as commit dependency

  • From: Andreas Ringlstetter <andreas.ringlstetter@xxxxxxxxxxxxxxxxxxxx>
  • To: <codeface@xxxxxxxxxxxxx>
  • Date: Thu, 20 Aug 2015 11:22:44 +0200

Hi Claus,

in def performAnalysis, you wrote

entity_type = ("Function")
is this intentional or should that actually be
entity_type = ("Function", )

Same in def writeIDwithCmtStats2File:
logical_depends, cmtlist, dbm, conf, entity_type=("Function"),
should probably be
logical_depends, cmtlist, dbm, conf, entity_type=("Function", ),

Greetings,
Andreas

Am 20.08.2015 um 11:06 schrieb Claus Hunsen:
Hi Wolfgang,

thank you for your comments. I will take care of everything and open a
pull request on GitHub afterwards, if nobody has further remarks.

Best,
Claus


Am 19.08.2015 um 23:52 schrieb Wolfgang Mauerer:
Dear Claus,

thanks for making the code available to the public! Some small comments
on technical details are inline.

The patch is fairly large -- could you please at least generate one for
the added functionality, and one for the updated test cases? This should
be quite easy since the two things reside in different sets of files.

Am 10/08/2015 um 12:18 schrieb Claus Hunsen:
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.

- adjusted feature extraction and storage mechanisms to handle also feature
expressions
- adjusted emittance of data to the DB for matching new method signatures
(tuples!)
- adjusted the tests accordingly (100% OK)

Signed-off-by: Claus Hunsen <hunsen@xxxxxxxxxxxxxxxxx>
---
codeface/VCS.py | 80 +++++++++++++++----
codeface/cluster/cluster.py | 75 ++++++++++++------
codeface/fileCommit.py | 9 ++-
codeface/test/integration/test_features.py | 29 ++++++-
codeface/test/unit/test_cppstats_works.py | 13 ++-
codeface/test/unit/test_getFeatureLines.py | 123
++++++++++++++++++++++-------
6 files changed, 252 insertions(+), 77 deletions(-)

diff --git a/codeface/VCS.py b/codeface/VCS.py
index a4f569d..fbffb63 100644
--- a/codeface/VCS.py
+++ b/codeface/VCS.py
@@ -272,7 +272,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 "
@@ -286,37 +288,42 @@ def get_feature_lines(parsed_lines, filename):
calculates an dictionary representing the feature sets for any line
of the given file.
: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, [])

the patch often refers to feature lines and feature expressions;
however, the difference between the two (or their exact meaning,
respectively) does not seem to be documented anywhere.


+ 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)
+ # (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)
# 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 +339,21 @@ def get_feature_lines(parsed_lines, filename):
# 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 +379,11 @@ def get_feature_lines(parsed_lines, filename):
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 +412,33 @@ def get_feature_lines(parsed_lines, filename):
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 +484,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 +505,10 @@ def get_feature_lines_from_file(file_layout_src,
filename):
empty = FileDict()
empty.add_line(0, [])
feature_lines = empty
+ fexpr_lines = empty
+
# save result to the file commit instance
- return feature_lines
+ return (feature_lines, fexpr_lines)

the above comment is (and has also previously been) a bit misleading
since nothing is saved at this point.

class gitVCS (VCS):
def __init__(self):
diff --git a/codeface/cluster/cluster.py b/codeface/cluster/cluster.py
index 6f917a8..f07ad22 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
+ logical_depends - tuple of dictionary key=cmthash, value=list of
co-changed subroutines (i.e., functions)
+ 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,19 @@ def writeDependsToDB(
# 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
+ for entity_type_current, logical_depends_current in zip(entity_type,
logical_depends):
+
+ for cmt in cmtlist:
+ if (cmt.id in logical_depends_current) &
(logical_depends_current is not None):
+ key = dbm.getCommitId(projectID, cmt.id)
+ depends_list = logical_depends_current[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_current, count,
impl) for file, entityId, count, impl in depends_list]
+ cmt_depend_rows.extend(rows)
very long lines here


# Perform batch insert
dbm.doExecCommit("INSERT INTO commit_dependency (commitId, file,
entityId, entityType, size, impl)" +
@@ -1323,8 +1325,8 @@ def writeAdjMatrixMaxWeight2File(id_mgr, outdir,
conf):
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:
@@ -1451,8 +1453,10 @@ def
compute_logical_depends_features(file_commit_list, cmt_dict, start_date):
'''

feature_depends_count = {}
+ fexpr_depends_count = {}

see comment on features and features expressions above.
for file in file_commit_list.values():
feature_depends = {}
+ fexpr_depends = {}
filename = file.getFilename()
idx = file.getIndx()
for line_num in idx:
@@ -1461,10 +1465,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 +1480,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 +1494,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 +1835,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,23 +1857,24 @@ 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)
logical_depends = compute_logical_depends_features(
- fileCommitDict, cmtdict, startDate)
+ fileCommitDict, cmtdict, startDate) # already a tuple

not clear to me what this comment does intend to say.

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 +1884,7 @@ def performAnalysis(conf, dbm, dbfilename, git_repo,
revrange, subsys_descr,
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 6857a78..9a502e1 100644
--- a/codeface/fileCommit.py
+++ b/codeface/fileCommit.py
@@ -119,8 +119,9 @@ class FileCommit:
# meta data
self._src_elem_list = []

- # dictionary with key = line number, value = feature list
+ # dictionary with key = line number, value = feature list|feature
expression
self.feature_info = FileDict()
+ self.feature_expression_info = FileDict()
I suppose the regexp at the end of this comment applies to feature_info
and then featur_expression -- in that case, it should be "dictionaries"
to clarify things.

#Getter/Setters
def getFileSnapShots(self):
@@ -154,7 +155,8 @@ class FileCommit:
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 @@ class FileCommit:

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)
diff --git a/codeface/test/integration/test_features.py
b/codeface/test/integration/test_features.py
index fb78393..ffd0510 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 edd027e..c401c24 100644
--- a/codeface/test/unit/test_cppstats_works.py
+++ b/codeface/test/unit/test_cppstats_works.py
@@ -67,7 +67,7 @@ class TestCppStatsWorks(unittest.TestCase):
#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 @@ class TestCppStatsWorks(unittest.TestCase):
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 4d18967..3de29ed 100644
--- a/codeface/test/unit/test_getFeatureLines.py
+++ b/codeface/test/unit/test_getFeatureLines.py
@@ -73,33 +73,37 @@ class TestFeatureLineParsing(unittest.TestCase):

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 @@ class TestFeatureLines(unittest.TestCase):
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 @@ class TestFeatureLines(unittest.TestCase):
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 @@ class TestFeatureLines(unittest.TestCase):
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 @@ class TestFeatureLines(unittest.TestCase):
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 @@ class TestFeatureLines(unittest.TestCase):
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 @@ class TestFeatureLines(unittest.TestCase):
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



Thanks & best regards, Wolfgang




Other related posts: