Skip to content

Commit 5a896be

Browse files
authored
Merge pull request #1 from marklogic-community/hansenmc-code-coverage
Looks good, and indeed seems to be faster. I ran a few tests with modules that import a lib but don't execute any of it's methods, and it still sets breakpoints and collects Wanted lines, so skipping when we already have an entry is a great optimization.
2 parents c58c302 + 0219785 commit 5a896be

File tree

3 files changed

+57
-38
lines changed

3 files changed

+57
-38
lines changed

src/main/ml-modules/root/test/test-coverage.xqy

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -70,49 +70,69 @@ declare private function cover:_task-cancel-safe(
7070
try {
7171
(:TODO is this a good idea, or a bad idea??:)
7272
for $breakpoint in dbg:breakpoints($id) return dbg:clear($id, $breakpoint),
73-
xdmp:request-cancel(xdmp:host(), xdmp:server("TaskServer"), $id) }
73+
dbg:detach($id),
74+
if (fn:empty(dbg:wait($id, 10))) then
75+
fn:error(xs:QName("FAILED-TO-CANCEL"), "unable to cancel a debugging request")
76+
else ()
77+
}
7478
catch ($ex) {
7579
if ($ex/error:code eq 'XDMP-NOREQUEST') then ()
76-
else xdmp:rethrow() }
80+
else xdmp:rethrow()
81+
}
7782
};
7883

84+
(:
85+
: @param $request the ID of a debug request
86+
: @param $uri URI of a module whose coverage we are observing
87+
: @param $limit maximum number of lines we'll try to observe
88+
: @param $results-map map:map containing line numbers
89+
:)
7990
declare private function cover:_prepare-from-request(
8091
$request as xs:unsignedLong,
8192
$uri as xs:string,
8293
$limit as xs:integer,
8394
$results-map as map:map)
8495
{
85-
helper:log(text {'cover:_prepare-from-request', ('request', $request, 'module', $uri)}),
8696
try {
87-
let $lines-map := map:get($results-map, $uri)[2]
88-
(: Semi-infinite loop, to be broken using DBG-LINE.
89-
: This avoids stack overflow errors.
90-
:)
91-
for $line in 1 to $limit
92-
(: We only need to break once per line, but we set a breakpoint
93-
: on every expression to maximize the odds of seeing that line.
94-
: But dbg:line will return the same expression more than once,
95-
: at the start of a module or when it sees an expression
96-
: that covers multiple lines. So we call dbg:expr for the right info.
97-
: Faster to loop once and call dbg:expr many extra times,
98-
: or gather all unique expr-ids and then loop again?
99-
: Because of the loop-break technique, one loop is easier.
100-
:)
101-
for $expr-id in dbg:line($request, $uri, $line)
102-
let $set := dbg:break($request, $expr-id)
103-
let $expr := dbg:expr($request, $expr-id)
104-
let $key := $expr/dbg:line/fn:string()
105-
where fn:not(map:get($lines-map, $key))
106-
return cover:_put($lines-map, $key),
97+
let $wanted-lines-map := map:get($results-map, $uri)[2]
98+
return
99+
if (fn:count(map:keys($wanted-lines-map)) > 0) then
100+
(: We've already gathered information on these lines :)
101+
()
102+
else (
103+
helper:log(text {'cover:_prepare-from-request', ('request', $request, 'module', $uri)}),
104+
(: Semi-infinite loop, to be broken using DBG-LINE.
105+
: This avoids stack overflow errors.
106+
:)
107+
for $line in 1 to $limit
108+
(: We only need to break once per line, but we set a breakpoint
109+
: on every expression to maximize the odds of seeing that line.
110+
: But dbg:line will return the same expression more than once,
111+
: at the start of a module or when it sees an expression
112+
: that covers multiple lines. So we call dbg:expr for the right info.
113+
: Faster to loop once and call dbg:expr many extra times,
114+
: or gather all unique expr-ids and then loop again?
115+
: Because of the loop-break technique, one loop is easier.
116+
:)
117+
for $expr-id in dbg:line($request, $uri, $line)
118+
let $set := dbg:break($request, $expr-id)
119+
let $expr := dbg:expr($request, $expr-id)
120+
let $key := $expr/dbg:line/fn:string()
121+
where fn:not(map:get($wanted-lines-map, $key))
122+
return cover:_put($wanted-lines-map, $key),
107123

108-
(: We should always hit EOF and DBG-LINE before this.
109-
: Tell the caller that we could not do it.
110-
:)
111-
cover:_task-cancel-safe($request),
112-
fn:error((), 'UNIT-TEST-TOOBIG', ('Module is too large for code coverage limit:', $limit))
124+
(: We should always hit EOF and DBG-LINE before this.
125+
: Tell the caller that we could not do it.
126+
:)
127+
cover:_task-cancel-safe($request),
128+
fn:error((), 'UNIT-TEST-TOOBIG', ('Module is too large for code coverage limit:', $limit))
129+
)
113130
} catch ($ex) {
114131
if ($ex/error:code = "DBG-LINE") then ()
115-
else if ($ex/error:code = ("DBG-MODULEDNE", "DBG-REQUESTRECORD", "XDMP-MODNOTFOUND")) then
132+
else if ($ex/error:code = "DBG-MODULEDNE") then
133+
(: Modules are not visible to this request unless they are used by the initiating main module. :)
134+
()
135+
else if ($ex/error:code = ("DBG-REQUESTRECORD", "XDMP-MODNOTFOUND")) then
116136
helper:log("Error executing " || $uri || " " || ($ex/*:code))
117137
else
118138
(
@@ -125,6 +145,10 @@ declare private function cover:_prepare-from-request(
125145

126146
(:~
127147
: This function prepares code coverage information for the specified modules.
148+
:
149+
: @return map:map where the keys are module URIs and the values are a pair of maps. The first map's keys are the
150+
: lines that have code coverage (empty after this function); the second's keys are the lines that
151+
: we want to have covered.
128152
:)
129153
declare private function cover:_prepare(
130154
$coverage-modules as xs:string+,
@@ -226,7 +250,8 @@ declare function cover:results(
226250
(:~
227251
: Return a list of the XQuery modules eligible for code coverage.
228252
:)
229-
declare function list-coverage-modules() as xs:string* {
253+
declare function list-coverage-modules() as xs:string*
254+
{
230255
let $database-id as xs:unsignedLong := xdmp:modules-database()
231256
let $modules-root as xs:string := xdmp:modules-root()
232257
let $module-extensions as xs:string* := (".xqy", ".xqe", ".xq", ".xquery")
@@ -433,4 +458,4 @@ declare function cover:module-view(
433458
}
434459
return
435460
cover:module-view($module, $format, $source, cover:_map-from-sequence($wanted), cover:_map-from-sequence($covered))
436-
};
461+
};

src/test/ml-modules/root/test/suites/More Unit Tests/assert-true.xqy

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ test:assert-true((fn:true(), fn:true())),
2525
test:assert-throws-error(xdmp:function(xs:QName("local:case1")), "ASSERT-TRUE-FAILED"),
2626
test:assert-throws-error(xdmp:function(xs:QName("local:case2")), "ASSERT-TRUE-FAILED"),
2727

28-
(: Causing this one to fail on purpose :)
29-
test:assert-true(fn:false(), "test"),
30-
3128
test:assert-true((fn:true(), fn:true()), "test"),
3229
test:assert-throws-error(xdmp:function(xs:QName("local:case3")), "ASSERT-TRUE-FAILED"),
3330
test:assert-throws-error(xdmp:function(xs:QName("local:case4")), "ASSERT-TRUE-FAILED")

src/test/ml-modules/root/test/suites/Unit Test Tests/assert-false.xqy

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,5 @@ declare function local:case2()
1212

1313
test:assert-false(fn:false()),
1414

15-
(: Causing this one to fail on purpose :)
16-
test:assert-false((fn:true(), fn:false())),
17-
1815
test:assert-throws-error(xdmp:function(xs:QName("local:case1")), "ASSERT-FALSE-FAILED"),
19-
test:assert-throws-error(xdmp:function(xs:QName("local:case2")), "ASSERT-FALSE-FAILED")
16+
test:assert-throws-error(xdmp:function(xs:QName("local:case2")), "ASSERT-FALSE-FAILED")

0 commit comments

Comments
 (0)