Skip to content

Commit

Permalink
cov: store "is branch" in file coverage segment
Browse files Browse the repository at this point in the history
Now closer to the function coverage  region storage format. Added tests for
the full  coverage information  in the file  segmentation test  and updated
JSON diff to cover two models of  the superset addition -- "add field" (can
be ignored in most of the cases) and  "add list item" (is not ignore as the
lists are mostly  asserted to contain a specific number  of elements -- not
"starts with")
  • Loading branch information
haxscramper committed Apr 22, 2024
1 parent d981f4e commit 90d9763
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 23 deletions.
81 changes: 67 additions & 14 deletions scripts/cxx_codegen/profdata_merger/profdata_merger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <llvm/ADT/Hashing.h>
#include <hstd/system/Formatter.hpp>
#include <algorithm>
#include <absl/hash/hash.h>

#include <perfetto.h>

Expand Down Expand Up @@ -779,6 +780,7 @@ struct queries {
"SegmentIndex", // 11
"NestedIn", // 12
"IsLeaf", // 13
"IsBranch", // 14
}))
,
// ---
Expand Down Expand Up @@ -885,6 +887,7 @@ struct CountedRegionHasher {
}
};


struct CountedRegionComparator {
size_t operator()(
const CounterMappingRegion& lhs,
Expand All @@ -898,6 +901,35 @@ struct CountedRegionComparator {
}
};


struct FileSpan {
unsigned int LineStart{};
unsigned int LineEnd{};
unsigned int ColStart{};
unsigned int ColEnd{};
};

struct FileSpanHasher {
size_t operator()(const FileSpan& region) const {
return hash_combine(
region.LineStart,
region.LineEnd,
region.ColStart,
region.ColEnd);
}
};


struct FileSpanComparator {
size_t operator()(const FileSpan& lhs, const FileSpan& rhs) const {
return lhs.LineStart == rhs.LineStart //
&& lhs.ColStart == rhs.ColStart //
&& lhs.LineEnd == rhs.LineEnd //
&& lhs.ColEnd == rhs.ColEnd;
}
};


struct db_build_ctx {
int context_id{};
int instantiation_id{};
Expand Down Expand Up @@ -1141,6 +1173,32 @@ void add_file(CoverageData const& file, queries& q, db_build_ctx& ctx) {
return lhs.first.self_id < rhs.first.self_id;
});

std::unordered_map<FileSpan, bool, FileSpanHasher, FileSpanComparator>
branch_cache{};

for (auto it : enumerate(file.getBranches())) {
CountedRegion const& r = it.value();
branch_cache.insert_or_assign(
FileSpan{
.LineStart = r.LineStart,
.LineEnd = r.LineEnd,
.ColStart = r.ColumnStart,
.ColEnd = r.ColumnEnd,
},
true);
q.file_branch.bind(1, ctx.context_id);
q.file_branch.bind(2, (int64_t)r.ExecutionCount);
q.file_branch.bind(3, (int64_t)r.FalseExecutionCount);
q.file_branch.bind(4, r.Folded);
q.file_branch.bind(5, (int64_t)r.LineStart);
q.file_branch.bind(6, (int64_t)r.ColumnStart);
q.file_branch.bind(7, (int64_t)r.LineEnd);
q.file_branch.bind(8, (int64_t)r.ColumnEnd);
q.func_region.bind(9, r.Kind);
q.file_branch.exec();
q.file_branch.reset();
}

for (auto it : enumerate(segment_pairs)) {
auto const& [nesting, end] = it.value();
auto const& start = nesting.segment;
Expand All @@ -1162,24 +1220,19 @@ void add_file(CoverageData const& file, queries& q, db_build_ctx& ctx) {
q.segment.bind(12, nullptr);
}
q.segment.bind(13, nesting.is_leaf);

FileSpan span{
.LineStart = start.Line,
.LineEnd = end.Line,
.ColStart = start.Col,
.ColEnd = end.Col,
};

q.segment.bind(14, branch_cache.contains(span));
q.segment.exec();
q.segment.reset();
}

for (auto it : enumerate(file.getBranches())) {
CountedRegion const& r = it.value();
q.file_branch.bind(1, ctx.context_id);
q.file_branch.bind(2, (int64_t)r.ExecutionCount);
q.file_branch.bind(3, (int64_t)r.FalseExecutionCount);
q.file_branch.bind(4, r.Folded);
q.file_branch.bind(5, (int64_t)r.LineStart);
q.file_branch.bind(6, (int64_t)r.ColumnStart);
q.file_branch.bind(7, (int64_t)r.LineEnd);
q.file_branch.bind(8, (int64_t)r.ColumnEnd);
q.func_region.bind(9, r.Kind);
q.file_branch.exec();
q.file_branch.reset();
}

for (ExpansionRecord const& e : file.getExpansions()) {
int function_id = get_function_id(e.Function, q, ctx);
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx_codegen/profdata_merger/profdata_merger.sql
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ CREATE TABLE "CovSegment" (
"SegmentIndex" INTEGER NOT NULL,
"NestedIn" INTEGER,
"IsLeaf" BOOLEAN NOT NULL,
"IsBranch" BOOLEAN NOT NULL,
PRIMARY KEY ("Id"),
FOREIGN KEY("File") REFERENCES "CovFile" ("Id"),
FOREIGN KEY("Context") REFERENCES "CovContext" ("Id"),
Expand Down
1 change: 1 addition & 0 deletions scripts/py_repository/py_repository/gen_coverage_cxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class CovSegment(CoverageSchema):
SegmentIndex = IntColumn()
NestedIn = ForeignId("CovSegment.Id", nullable=True)
IsLeaf = BoolColumn()
IsBranch = BoolColumn()

def intersects(self, line: int, col: int) -> bool:
return (self.LineStart <= line <= self.LineEnd) and (self.ColStart <= col <=
Expand Down
28 changes: 20 additions & 8 deletions scripts/py_scriptutils/py_scriptutils/json_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
class Op(Enum):
Replace = auto()
Remove = auto()
Add = auto()
AddField = auto()
AppendItem = auto()


@beartype
Expand All @@ -39,7 +40,7 @@ def json_diff(source: Json,
result.append(DiffItem(Op.Replace, path, target))
return result

if isinstance(source, list):
if isinstance(source, (list, tuple)):
i = 0
while i < len(source) and i < len(target):
temp_diff = json_diff(source[i], target[i], path.child(Index(i)), ignore)
Expand All @@ -52,9 +53,11 @@ def json_diff(source: Json,
i += 1

while i < len(target):
result.append(DiffItem(Op.Add, path.child(Index(i)), target[i]))
result.append(DiffItem(Op.AppendItem, path.child(Index(i)), target[i]))
i += 1

assert i == max(len(target), len(source))

elif isinstance(source, dict):
for key in source:
if key in target:
Expand All @@ -67,7 +70,7 @@ def json_diff(source: Json,

for key in target:
if key not in source:
result.append(DiffItem(Op.Add, path.child(Fields(key)), target[key]))
result.append(DiffItem(Op.AddField, path.child(Fields(key)), target[key]))

else:
result.append(DiffItem(Op.Replace, path, target))
Expand All @@ -84,7 +87,8 @@ def get_subset_diff(main_set: Json, expected_subset: Json) -> List[DiffItem]:
# If some element from expect *sub*set was added, it is an expected behavior. All other operations
# are returned.
return [
it for it in json_diff(source=expected_subset, target=main_set) if it.op != Op.Add
it for it in json_diff(source=expected_subset, target=main_set)
if it.op != Op.AddField
]


Expand All @@ -101,8 +105,13 @@ def describe_diff(

if it.op == Op.Remove:
description += f"{source_name} has extra entry"
elif it.op == Op.Add:
description += f"{target_name} missing entry"

elif it.op == Op.AddField:
description += f"{target_name} missing field"

elif it.op == Op.AppendItem:
description += f"{target_name} missing list item"

elif it.op == Op.Replace:
description += "changed entry"

Expand All @@ -115,7 +124,10 @@ def get_path(value: Json) -> Json:
if it.op == Op.Remove:
description += f" {json.dumps(get_path(source), indent=2)}"

elif it.op == Op.Add:
elif it.op == Op.AddField:
description += f" {json.dumps(it.value, indent=2)}"

elif it.op == Op.AppendItem:
description += f" {json.dumps(it.value, indent=2)}"

elif it.op == Op.Replace:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ void coverage_branches(int arg) {
} else {
action();
}

if (arg == 0 || arg == 1 || arg == 2
|| ((arg <= 5 || arg <= 5) && arg < 10)) {
action();
}
}


Expand Down
21 changes: 20 additions & 1 deletion tests/python/repo/test_code_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,21 +326,40 @@ def test_file_segmentation_1():
session = open_sqlite_session(cmd.get_sqlite(), cov.CoverageSchema)
main_cov = cov.get_coverage_of(session, cmd.get_code("main.cpp"))
lines = code.split("\n")
# print(format_db_all(session))

segtree = cov.CoverageSegmentTree(it[0] for it in session.execute(main_cov))
df = pd.read_sql(main_cov, session.get_bind())
add_cov_segment_text(df, lines)

# print(render_rich(dataframe_to_rich_table(df)))

# Coverage segments only overlay executable blocks and do not
# Coverage segments only overlay executable blocks and do not
# account for extraneous elements such as function headers etc.
assert segtree.query(line=1, col=15)
assert not segtree.query(line=1, col=14)
assert_frame(df[df["LineStart"] == 1], [
dict(IsLeaf=True, Text="{}", ColStart=15, ColEnd=17),
])

# print(render_rich(dataframe_to_rich_table(df[df["IsBranch"] == True])))
assert_frame(df[df["IsBranch"] == True], [
dict(LineStart=3, ColStart=9, LineEnd=3, ColEnd=16, Text="arg > 0"),
dict(LineStart=4, ColStart=13, LineEnd=4, ColEnd=21, Text="arg > 10"),
dict(LineStart=5, ColStart=17, LineEnd=5, ColEnd=25, Text="arg > 20"),
dict(LineStart=11, ColStart=17, LineEnd=11, ColEnd=24, Text="arg > 5"),
dict(LineStart=17, ColStart=16, LineEnd=17, ColEnd=23, Text="arg < 0"),
dict(LineStart=18, ColStart=13, LineEnd=18, ColEnd=22, Text="arg < -10"),
dict(LineStart=19, ColStart=17, LineEnd=19, ColEnd=26, Text="arg < -20"),
dict(LineStart=25, ColStart=17, LineEnd=25, ColEnd=25, Text="arg < -5"),
dict(LineStart=35, ColStart=9, LineEnd=35, ColEnd=17, Text="arg == 0"),
dict(LineStart=35, ColStart=21, LineEnd=35, ColEnd=29, Text="arg == 1"),
dict(LineStart=35, ColStart=33, LineEnd=35, ColEnd=41, Text="arg == 2"),
dict(LineStart=36, ColStart=14, LineEnd=36, ColEnd=22, Text="arg <= 5"),
dict(LineStart=36, ColStart=26, LineEnd=36, ColEnd=34, Text="arg <= 5"),
dict(LineStart=36, ColStart=39, LineEnd=36, ColEnd=47, Text="arg < 10"),
])


def test_file_segmentation_2():
with TemporaryDirectory() as tmp:
Expand Down

0 comments on commit 90d9763

Please sign in to comment.