Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0d69d63

Browse files
authored
Merge pull request #604 from utPLSQL/feature/12cblockcoverage
12.2 Oracle Native Block Coverage
2 parents 631a55e + 2d6c7d3 commit 0d69d63

43 files changed

Lines changed: 1583 additions & 318 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/userguide/coverage.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,40 @@ The report allow you to navigate to each source file and inspect line by line co
5353
![Coverage Details page](../images/coverage_html_details.png)
5454

5555

56+
#### Oracle 12.2 block coverage.
57+
In Oracle 12.2 new functionality was released which supports native [block coverage](https://docs.oracle.com/en/database/oracle/oracle-database/12.2/arpls/DBMS_PLSQL_CODE_COVERAGE.html#GUID-55A9E502-9EC2-4118-B292-DC79E6DC465E).
58+
This has been enabled in utPLSQL code coverage as a separate option. It can be invoked by passing a argument a_coverage_type with value 'block'. By default profiler option is enabled ('proftab').
59+
60+
Example:
61+
```sql
62+
begin
63+
ut.run(ut_coverage_html_reporter(),a_coverage_type => 'block');
64+
end;
65+
/
66+
```
67+
68+
In this mode html reporter will show additionally number of lines that been partially covered and highlight them in orange. Number of blocks in code, blocks covered and missed.
69+
70+
#### Oracle 12.2 extended coverage with profiler and block coverage
71+
Using data collected from profiler and block coverage running parallel we are able to enrich information about coverage.
72+
For every line recorded by profiler if we have a partially covered same line in block coverage we will display that information
73+
presenting line as partially covered and displaying number of block and how many blocks been covered in that line.
74+
75+
Example:
76+
```sql
77+
begin
78+
ut.run(ut_coverage_html_reporter(),a_coverage_type => 'extended');
79+
end;
80+
/
81+
```
82+
83+
Sample output:
84+
![Package Coverage Summary](../images/extended_coverage_html_summary.png)
85+
86+
![Line Coverage Details](../images/extended_coverage_html_line.png)
87+
88+
89+
5690
### Coverage reporting options
5791

5892
There are two distinct ways to gather code coverage:

source/api/ut.pkb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ create or replace package body ut is
113113
begin
114114
ut_runner.run(
115115
a_paths, ut_reporters(coalesce(a_reporter,ut_documentation_reporter())),
116-
ut_utils.int_to_boolean(a_color_console), a_coverage_schemes,
117-
a_source_file_mappings, a_test_file_mappings, a_include_objects, a_exclude_objects
116+
ut_utils.int_to_boolean(a_color_console), a_coverage_schemes, a_source_file_mappings,
117+
a_test_file_mappings, a_include_objects, a_exclude_objects, false
118118
);
119119
rollback;
120120
end;
@@ -131,7 +131,7 @@ create or replace package body ut is
131131
ut_utils.int_to_boolean(a_color_console), a_coverage_schemes,
132132
ut_file_mapper.build_file_mappings(a_source_files),
133133
ut_file_mapper.build_file_mappings(a_test_files),
134-
a_include_objects, a_exclude_objects
134+
a_include_objects, a_exclude_objects, false
135135
);
136136
rollback;
137137
end;
@@ -376,7 +376,7 @@ create or replace package body ut is
376376
begin
377377
ut.run(
378378
l_paths, a_reporter, a_color_console, a_coverage_schemes, a_source_files, a_test_files,
379-
a_include_objects, a_exclude_objects
379+
a_include_objects, a_exclude_objects
380380
);
381381
end;
382382

source/api/ut_runner.pks

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ create or replace package ut_runner authid current_user is
5858
procedure run(
5959
a_paths ut_varchar2_list, a_reporters ut_reporters, a_color_console boolean := false,
6060
a_coverage_schemes ut_varchar2_list := null, a_source_file_mappings ut_file_mappings := null, a_test_file_mappings ut_file_mappings := null,
61-
a_include_objects ut_varchar2_list := null, a_exclude_objects ut_varchar2_list := null, a_fail_on_errors boolean default false
61+
a_include_objects ut_varchar2_list := null, a_exclude_objects ut_varchar2_list := null,
62+
a_fail_on_errors boolean default false
6263
);
6364

6465
/**
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
begin
2+
$if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then
3+
dbms_plsql_code_coverage.create_coverage_tables(force_it => true);
4+
$else
5+
null;
6+
$end
7+
end;
8+
/

source/core/coverage/ut_coverage.pkb

Lines changed: 67 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,10 @@ create or replace package body ut_coverage is
1616
limitations under the License.
1717
*/
1818

19+
1920
type t_source_lines is table of binary_integer;
2021

21-
-- The source query has two important transformations done in it.
22-
-- the flag: to_be_skipped ='Y' is set for a line of code that is badly reported by DBMS_PROFILER as executed 0 times.
23-
-- This includes lines that are:
24-
-- - PACKAGE, PROCEDURE, FUNCTION definition line,
25-
-- - BEGIN, END of a block
26-
-- Another transformation is adjustment of line number for TRIGGER body.
27-
-- DBMS_PROFILER is reporting line numbers for triggers not as defined in DBA_SOURCE, its usign line numbers as defined in DBA_TRIGGERS
28-
-- the DBA_TRIGGERS does not contain the trigger specification lines, only lines that define the trigger body.
29-
-- the query adjusts the line numbers for triggers by finding first occurrence of begin|declare|compound in the trigger body line.
30-
-- The subquery is optimized by:
31-
-- - COALESCE function -> it will execute only for TRIGGERS
32-
-- - scalar subquery cache -> it will only execute once for one trigger source code.
33-
function get_cov_sources_sql(a_coverage_options ut_coverage_options) return varchar2 is
22+
function get_cov_sources_sql(a_coverage_options ut_coverage_options, a_skipped_lines varchar2 default 'Y') return varchar2 is
3423
l_result varchar2(32767);
3524
l_full_name varchar2(100);
3625
l_view_name varchar2(200) := ut_metadata.get_dba_view('dba_source');
@@ -54,8 +43,10 @@ create or replace package body ut_coverage is
5443
where t.owner = s.owner and t.type = s.type and t.name = s.name
5544
and regexp_like( t.text, '[A-Za-z0-9$#_]*(begin|declare|compound).*','i'))
5645
) as line,
57-
s.text,
58-
case
46+
s.text, ]';
47+
if a_skipped_lines = 'Y' then
48+
l_result := l_result ||
49+
q'[case
5950
when
6051
-- to avoid execution of regexp_like on every line
6152
-- first do a rough check for existence of search pattern keyword
@@ -70,8 +61,13 @@ create or replace package body ut_coverage is
7061
'^([\t ]*(((not)?\s*(overriding|final|instantiable)[\t ]*)*(static|constructor|member)?[\t ]*(procedure|function)|package([\t ]+body)|begin|end([\t ]+\S+)*[ \t]*;))', 'i'
7162
)
7263
then 'Y'
73-
end as to_be_skipped
74-
from ]'||l_view_name||q'[ s]';
64+
end as to_be_skipped ]';
65+
else
66+
l_result := l_result || q'['N' as to_be_skipped ]';
67+
end if;
68+
69+
l_result := l_result ||' from '||l_view_name||q'[ s]';
70+
7571
if a_coverage_options.file_mappings is not empty then
7672
l_result := l_result || '
7773
join table(:file_mappings) f
@@ -95,7 +91,7 @@ create or replace package body ut_coverage is
9591
return l_result;
9692
end;
9793

98-
function get_cov_sources_cursor(a_coverage_options ut_coverage_options) return sys_refcursor is
94+
function get_cov_sources_cursor(a_coverage_options in ut_coverage_options,a_sql in varchar2) return sys_refcursor is
9995
l_cursor sys_refcursor;
10096
l_skip_objects ut_object_names;
10197
l_sql varchar2(32767);
@@ -104,7 +100,7 @@ create or replace package body ut_coverage is
104100
--skip all the utplsql framework objects and all the unit test packages that could potentially be reported by coverage.
105101
l_skip_objects := ut_utils.get_utplsql_objects_list() multiset union all coalesce(a_coverage_options.exclude_objects, ut_object_names());
106102
end if;
107-
l_sql := get_cov_sources_sql(a_coverage_options);
103+
l_sql := a_sql;
108104
if a_coverage_options.file_mappings is not empty then
109105
open l_cursor for l_sql using a_coverage_options.file_mappings, l_skip_objects;
110106
elsif a_coverage_options.include_objects is not empty then
@@ -115,7 +111,7 @@ create or replace package body ut_coverage is
115111
return l_cursor;
116112
end;
117113

118-
procedure populate_tmp_table(a_coverage_options ut_coverage_options) is
114+
procedure populate_tmp_table(a_coverage_options in ut_coverage_options,a_sql in varchar2) is
119115
pragma autonomous_transaction;
120116
l_cov_sources_crsr sys_refcursor;
121117
l_cov_sources_data ut_coverage_helper.t_coverage_sources_tmp_rows;
@@ -124,7 +120,7 @@ create or replace package body ut_coverage is
124120
if not ut_coverage_helper.is_tmp_table_populated() or ut_coverage_helper.is_develop_mode() then
125121
ut_coverage_helper.cleanup_tmp_table();
126122

127-
l_cov_sources_crsr := get_cov_sources_cursor(a_coverage_options);
123+
l_cov_sources_crsr := get_cov_sources_cursor(a_coverage_options,a_sql);
128124

129125
loop
130126
fetch l_cov_sources_crsr bulk collect into l_cov_sources_data limit 1000;
@@ -143,14 +139,14 @@ create or replace package body ut_coverage is
143139
/**
144140
* Public functions
145141
*/
146-
procedure coverage_start is
142+
procedure coverage_start(a_coverage_options ut_coverage_options default null) is
147143
begin
148144
ut_coverage_helper.coverage_start('utPLSQL Code coverage run '||ut_utils.to_string(systimestamp));
149145
end;
150146

151-
procedure coverage_start_develop is
147+
procedure coverage_start_develop(a_coverage_options ut_coverage_options default null) is
152148
begin
153-
ut_coverage_helper.coverage_start_develop();
149+
ut_coverage_helper.coverage_start_develop;
154150
end;
155151

156152
procedure coverage_pause is
@@ -174,81 +170,54 @@ create or replace package body ut_coverage is
174170
end;
175171

176172
function get_coverage_data(a_coverage_options ut_coverage_options) return t_coverage is
177-
l_line_calls ut_coverage_helper.t_unit_line_calls;
178-
l_result t_coverage;
179-
l_new_unit t_unit_coverage;
180-
l_line_no binary_integer;
181-
l_source_objects_crsr ut_coverage_helper.t_tmp_table_objects_crsr;
182-
l_source_object ut_coverage_helper.t_tmp_table_object;
173+
l_result_block ut_coverage.t_coverage;
174+
l_result_profiler_enrich ut_coverage.t_coverage;
175+
l_object ut_coverage.t_full_name;
176+
l_line_no binary_integer;
183177
begin
184-
185-
--prepare global temp table with sources
186-
populate_tmp_table(a_coverage_options);
187-
188-
l_source_objects_crsr := ut_coverage_helper.get_tmp_table_objects_cursor();
189-
loop
190-
fetch l_source_objects_crsr into l_source_object;
191-
exit when l_source_objects_crsr%notfound;
192-
193-
--get coverage data
194-
l_line_calls := ut_coverage_helper.get_raw_coverage_data( l_source_object.owner, l_source_object.name );
195-
196-
--if there is coverage, we need to filter out the garbage (badly indicated data from dbms_profiler)
197-
if l_line_calls.count > 0 then
198-
--remove lines that should not be indicted as meaningful
199-
for i in 1 .. l_source_object.to_be_skipped_list.count loop
200-
if l_source_object.to_be_skipped_list(i) is not null then
201-
l_line_calls.delete(l_source_object.to_be_skipped_list(i));
202-
end if;
203-
end loop;
204-
end if;
205-
206-
--if there are no file mappings or object was actually captured by profiler
207-
if a_coverage_options.file_mappings is null or l_line_calls.count > 0 then
208-
209-
--populate total stats
210-
l_result.total_lines := l_result.total_lines + l_source_object.lines_count;
211-
212-
--populate object level coverage stats
213-
if not l_result.objects.exists(l_source_object.full_name) then
214-
l_result.objects(l_source_object.full_name) := l_new_unit;
215-
l_result.objects(l_source_object.full_name).owner := l_source_object.owner;
216-
l_result.objects(l_source_object.full_name).name := l_source_object.name;
217-
l_result.objects(l_source_object.full_name).total_lines := l_source_object.lines_count;
178+
-- Get raw data for both reporters, order is important as tmp table will skip headers and dont populate
179+
-- tmp table for block again.
180+
l_result_profiler_enrich:= ut_coverage_profiler.get_coverage_data(a_coverage_options => a_coverage_options);
181+
182+
-- If block coverage available we will use it.
183+
$if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then
184+
l_result_block := ut_coverage_block.get_coverage_data(a_coverage_options => a_coverage_options);
185+
186+
-- Enrich profiler results with some of the block results
187+
l_object := l_result_profiler_enrich.objects.first;
188+
while (l_object is not null)
189+
loop
190+
191+
l_line_no := l_result_profiler_enrich.objects(l_object).lines.first;
192+
193+
-- to avoid no data found check if we got object in profiler
194+
if l_result_block.objects.exists(l_object) then
195+
while (l_line_no is not null)
196+
loop
197+
-- To avoid no data check for object line
198+
if l_result_block.objects(l_object).lines.exists(l_line_no) then
199+
-- enrich line level stats
200+
l_result_profiler_enrich.objects(l_object).lines(l_line_no).partcove := l_result_block.objects(l_object).lines(l_line_no).partcove;
201+
l_result_profiler_enrich.objects(l_object).lines(l_line_no).covered_blocks := l_result_block.objects(l_object).lines(l_line_no).covered_blocks;
202+
l_result_profiler_enrich.objects(l_object).lines(l_line_no).no_blocks := l_result_block.objects(l_object).lines(l_line_no).no_blocks;
203+
-- enrich object level stats
204+
l_result_profiler_enrich.objects(l_object).partcovered_lines := nvl(l_result_profiler_enrich.objects(l_object).partcovered_lines,0) + l_result_block.objects(l_object).lines(l_line_no).partcove;
218205
end if;
219-
--map to results
220-
l_line_no := l_line_calls.first;
221-
if l_line_no is null then
222-
l_result.uncovered_lines := l_result.uncovered_lines + l_source_object.lines_count;
223-
l_result.objects(l_source_object.full_name).uncovered_lines := l_source_object.lines_count;
224-
else
225-
loop
226-
exit when l_line_no is null;
227-
228-
if l_line_calls(l_line_no) > 0 then
229-
--total stats
230-
l_result.covered_lines := l_result.covered_lines + 1;
231-
l_result.executions := l_result.executions + l_line_calls(l_line_no);
232-
--object level stats
233-
l_result.objects(l_source_object.full_name).covered_lines := l_result.objects(l_source_object.full_name).covered_lines + 1;
234-
l_result.objects(l_source_object.full_name).executions := l_result.objects(l_source_object.full_name).executions + l_line_calls(l_line_no);
235-
elsif l_line_calls(l_line_no) = 0 then
236-
l_result.uncovered_lines := l_result.uncovered_lines + 1;
237-
l_result.objects(l_source_object.full_name).uncovered_lines := l_result.objects(l_source_object.full_name).uncovered_lines + 1;
238-
end if;
239-
l_result.objects(l_source_object.full_name).lines(l_line_no) := l_line_calls(l_line_no);
240-
241-
l_line_no := l_line_calls.next(l_line_no);
242-
end loop;
243-
end if;
244-
end if;
245-
246-
end loop;
247-
248-
close l_source_objects_crsr;
249-
250-
return l_result;
251-
end get_coverage_data;
252-
206+
--At the end go to next line
207+
l_line_no := l_result_profiler_enrich.objects(l_object).lines.next(l_line_no);
208+
end loop;
209+
--total level stats enrich
210+
l_result_profiler_enrich.partcovered_lines := nvl(l_result_profiler_enrich.partcovered_lines,0) + l_result_profiler_enrich.objects(l_object).partcovered_lines;
211+
-- At the end go to next object
212+
end if;
213+
214+
l_object := l_result_profiler_enrich.objects.next(l_object);
215+
216+
end loop;
217+
$end
218+
219+
return l_result_profiler_enrich;
220+
end get_coverage_data;
221+
253222
end;
254223
/

0 commit comments

Comments
 (0)