|
| 1 | +CREATE OR REPLACE PACKAGE BODY ut_block_coverage IS |
| 2 | + /* |
| 3 | + utPLSQL - Version X.X.X.X |
| 4 | + Copyright 2016 - 2017 utPLSQL Project |
| 5 | + |
| 6 | + Licensed under the Apache License, Version 2.0 (the "License"): |
| 7 | + you may not use this file except in compliance with the License. |
| 8 | + You may obtain a copy of the License at |
| 9 | + |
| 10 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | + |
| 12 | + Unless required by applicable law or agreed to in writing, software |
| 13 | + distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | + See the License for the specific language governing permissions and |
| 16 | + limitations under the License. |
| 17 | + */ |
| 18 | + |
| 19 | + g_coverage_id INTEGER; |
| 20 | + |
| 21 | + TYPE t_unit_line_call IS RECORD( |
| 22 | + blocks BINARY_INTEGER DEFAULT 0 |
| 23 | + ,covered_blocks BINARY_INTEGER DEFAULT 0 |
| 24 | + ,partcovered BINARY_INTEGER DEFAULT 0); |
| 25 | + |
| 26 | + TYPE t_unit_line_calls IS TABLE OF t_unit_line_call INDEX BY BINARY_INTEGER; |
| 27 | + |
| 28 | + TYPE t_source_lines IS TABLE OF BINARY_INTEGER; |
| 29 | + |
| 30 | + -- The source query has two important transformations done in it. |
| 31 | + -- the flag: to_be_skipped ='Y' is set for a line of code that is badly reported by DBMS_PROFILER as executed 0 times. |
| 32 | + -- This includes lines that are: |
| 33 | + -- - PACKAGE, PROCEDURE, FUNCTION definition line, |
| 34 | + -- - BEGIN, END of a block |
| 35 | + -- Another transformation is adjustment of line number for TRIGGER body. |
| 36 | + -- DBMS_PROFILER is reporting line numbers for triggers not as defined in DBA_SOURCE, its usign line numbers as defined in DBA_TRIGGERS |
| 37 | + -- the DBA_TRIGGERS does not contain the trigger specification lines, only lines that define the trigger body. |
| 38 | + -- the query adjusts the line numbers for triggers by finding first occurrence of begin|declare|compound in the trigger body line. |
| 39 | + -- The subquery is optimized by: |
| 40 | + -- - COALESCE function -> it will execute only for TRIGGERS |
| 41 | + -- - scalar subquery cache -> it will only execute once for one trigger source code. |
| 42 | + FUNCTION get_cov_sources_sql(a_coverage_options ut_coverage_options) RETURN VARCHAR2 IS |
| 43 | + l_result VARCHAR2(32767); |
| 44 | + l_full_name VARCHAR2(100); |
| 45 | + l_view_name VARCHAR2(200) := ut_metadata.get_dba_view('dba_source'); |
| 46 | + BEGIN |
| 47 | + IF a_coverage_options.file_mappings IS NOT NULL AND |
| 48 | + a_coverage_options.file_mappings.count > 0 THEN |
| 49 | + l_full_name := 'f.file_name'; |
| 50 | + ELSE |
| 51 | + l_full_name := 'lower(s.owner||''.''||s.name)'; |
| 52 | + END IF; |
| 53 | + l_result := ' |
| 54 | + select full_name, owner, name, line, to_be_skipped, text |
| 55 | + from ( |
| 56 | + select ' || l_full_name || q'[ as full_name, |
| 57 | + s.owner, |
| 58 | + s.name, |
| 59 | + s.line - |
| 60 | + coalesce( |
| 61 | + case when type!='TRIGGER' then 0 end, |
| 62 | + (select min(t.line) - 1 |
| 63 | + from ]' || l_view_name || q'[ t |
| 64 | + where t.owner = s.owner and t.type = s.type and t.name = s.name |
| 65 | + and regexp_like( t.text, '[A-Za-z0-9$#_]*(begin|declare|compound).*','i')) |
| 66 | + ) as line, |
| 67 | + s.text, |
| 68 | + 'N' as to_be_skipped |
| 69 | + from ]' || l_view_name || q'[ s]'; |
| 70 | + IF a_coverage_options.file_mappings IS NOT empty THEN |
| 71 | + l_result := l_result || ' |
| 72 | + join table(:file_mappings) f |
| 73 | + on s.name = f.object_name |
| 74 | + and s.type = f.object_type |
| 75 | + and s.owner = f.object_owner |
| 76 | + where 1 = 1'; |
| 77 | + ELSIF a_coverage_options.include_objects IS NOT empty THEN |
| 78 | + l_result := l_result || ' |
| 79 | + where (s.owner, s.name) in (select il.owner, il.name from table(:include_objects) il)'; |
| 80 | + ELSE |
| 81 | + l_result := l_result || ' |
| 82 | + where s.owner in (select upper(t.column_value) from table(:l_schema_names) t)'; |
| 83 | + END IF; |
| 84 | + l_result := l_result || q'[ |
| 85 | + and s.type not in ('PACKAGE', 'TYPE', 'JAVA SOURCE') |
| 86 | + --Exclude calls to utPLSQL framework, Unit Test packages and objects from a_exclude_list parameter of coverage reporter |
| 87 | + and (s.owner, s.name) not in (select el.owner, el.name from table(:l_skipped_objects) el) |
| 88 | + ) |
| 89 | + where line > 0]'; |
| 90 | + RETURN l_result; |
| 91 | + END; |
| 92 | + |
| 93 | + --' /*Not mess formatter*/ |
| 94 | + |
| 95 | + FUNCTION get_cov_sources_cursor(a_coverage_options ut_coverage_options) |
| 96 | + RETURN SYS_REFCURSOR IS |
| 97 | + l_cursor SYS_REFCURSOR; |
| 98 | + l_skip_objects ut_object_names; |
| 99 | + l_schema_names ut_varchar2_rows; |
| 100 | + l_sql VARCHAR2(32767); |
| 101 | + BEGIN |
| 102 | + l_schema_names := coalesce(a_coverage_options.schema_names, |
| 103 | + ut_varchar2_rows(sys_context('USERENV', 'CURRENT_SCHEMA'))); |
| 104 | + IF NOT ut_coverage_helper.is_develop_mode() THEN |
| 105 | + --skip all the utplsql framework objects and all the unit test packages that could potentially be reported by coverage. |
| 106 | + l_skip_objects := ut_utils.get_utplsql_objects_list() MULTISET UNION ALL |
| 107 | + coalesce(a_coverage_options.exclude_objects, ut_object_names()); |
| 108 | + END IF; |
| 109 | + l_sql := get_cov_sources_sql(a_coverage_options); |
| 110 | + IF a_coverage_options.file_mappings IS NOT empty THEN |
| 111 | + OPEN l_cursor FOR l_sql |
| 112 | + USING a_coverage_options.file_mappings, l_skip_objects; |
| 113 | + ELSIF a_coverage_options.include_objects IS NOT empty THEN |
| 114 | + OPEN l_cursor FOR l_sql |
| 115 | + USING a_coverage_options.include_objects, l_skip_objects; |
| 116 | + ELSE |
| 117 | + OPEN l_cursor FOR l_sql |
| 118 | + USING l_schema_names, l_skip_objects; |
| 119 | + END IF; |
| 120 | + RETURN l_cursor; |
| 121 | + END; |
| 122 | + |
| 123 | + PROCEDURE populate_tmp_table(a_coverage_options ut_coverage_options) IS |
| 124 | + PRAGMA AUTONOMOUS_TRANSACTION; |
| 125 | + l_cov_sources_crsr SYS_REFCURSOR; |
| 126 | + l_cov_sources_data ut_coverage_helper.t_coverage_sources_tmp_rows; |
| 127 | + BEGIN |
| 128 | + |
| 129 | + IF NOT ut_coverage_helper.is_tmp_table_populated() OR |
| 130 | + ut_coverage_helper.is_develop_mode() THEN |
| 131 | + ut_coverage_helper.cleanup_tmp_table(); |
| 132 | + |
| 133 | + l_cov_sources_crsr := get_cov_sources_cursor(a_coverage_options); |
| 134 | + |
| 135 | + LOOP |
| 136 | + FETCH l_cov_sources_crsr BULK COLLECT |
| 137 | + INTO l_cov_sources_data LIMIT 1000; |
| 138 | + |
| 139 | + ut_coverage_helper.insert_into_tmp_table(l_cov_sources_data); |
| 140 | + |
| 141 | + EXIT WHEN l_cov_sources_crsr%NOTFOUND; |
| 142 | + END LOOP; |
| 143 | + |
| 144 | + CLOSE l_cov_sources_crsr; |
| 145 | + END IF; |
| 146 | + COMMIT; |
| 147 | + END; |
| 148 | + |
| 149 | + /** |
| 150 | + * Public functions |
| 151 | + */ |
| 152 | + PROCEDURE coverage_start IS |
| 153 | + BEGIN |
| 154 | + dbms_plsql_code_coverage.create_coverage_tables(force_it => TRUE); |
| 155 | + g_coverage_id := dbms_plsql_code_coverage.start_coverage(run_comment => 'utPLSQL Code coverage run ' || |
| 156 | + ut_utils.to_string(systimestamp)); |
| 157 | + END; |
| 158 | + |
| 159 | + PROCEDURE coverage_stop IS |
| 160 | + BEGIN |
| 161 | + dbms_plsql_code_coverage.stop_coverage; |
| 162 | + END; |
| 163 | + |
| 164 | + FUNCTION get_raw_coverage_data(a_object_owner VARCHAR2 |
| 165 | + ,a_object_name VARCHAR2) RETURN t_unit_line_calls IS |
| 166 | + TYPE coverage_row IS RECORD( |
| 167 | + line BINARY_INTEGER |
| 168 | + ,blocks BINARY_INTEGER |
| 169 | + ,covered_blocks BINARY_INTEGER); |
| 170 | + TYPE coverage_rows IS TABLE OF coverage_row; |
| 171 | + l_tmp_data coverage_rows; |
| 172 | + l_results t_unit_line_calls; |
| 173 | + |
| 174 | + l_debug NUMBER; |
| 175 | + BEGIN |
| 176 | + SELECT ccb.line |
| 177 | + ,COUNT(ccb.block) totalblocks |
| 178 | + ,SUM(ccb.covered) AS coveredblocks BULK COLLECT |
| 179 | + INTO l_tmp_data |
| 180 | + FROM dbmspcc_units ccu |
| 181 | + LEFT OUTER JOIN dbmspcc_blocks ccb |
| 182 | + ON ccu.run_id = ccb.run_id |
| 183 | + AND ccu.object_id = ccb.object_id |
| 184 | + WHERE ccu.owner = a_object_owner |
| 185 | + AND ccu.name = a_object_name |
| 186 | + AND ccu.run_id = g_coverage_id |
| 187 | + GROUP BY ccb.line |
| 188 | + ORDER BY 1; |
| 189 | + |
| 190 | + l_debug := l_tmp_data.count; |
| 191 | + |
| 192 | + FOR i IN 1 .. l_tmp_data.count |
| 193 | + LOOP |
| 194 | + l_results(l_tmp_data(i).line).blocks := l_tmp_data(i).blocks; |
| 195 | + l_results(l_tmp_data(i).line).covered_blocks := l_tmp_data(i).covered_blocks; |
| 196 | + l_results(l_tmp_data(i).line).partcovered := CASE |
| 197 | + WHEN (l_tmp_data(i).blocks > 1) AND |
| 198 | + (l_tmp_data(i).blocks > l_tmp_data(i) |
| 199 | + .covered_blocks) THEN |
| 200 | + 1 |
| 201 | + ELSE |
| 202 | + 0 |
| 203 | + END; |
| 204 | + END LOOP; |
| 205 | + RETURN l_results; |
| 206 | + END; |
| 207 | + |
| 208 | + PROCEDURE coverage_resume IS |
| 209 | + BEGIN |
| 210 | + NULL; |
| 211 | + END; |
| 212 | + |
| 213 | + PROCEDURE coverage_pause IS |
| 214 | + BEGIN |
| 215 | + NULL; |
| 216 | + END; |
| 217 | + |
| 218 | + FUNCTION get_coverage_data(a_coverage_options ut_coverage_options) |
| 219 | + RETURN ut_coverage.t_coverage IS |
| 220 | + l_line_calls t_unit_line_calls; |
| 221 | + l_result ut_coverage.t_coverage; |
| 222 | + l_new_unit ut_coverage.t_unit_coverage; |
| 223 | + line_no BINARY_INTEGER; |
| 224 | + l_source_objects_crsr ut_coverage_helper.t_tmp_table_objects_crsr; |
| 225 | + l_source_object ut_coverage_helper.t_tmp_table_object; |
| 226 | + BEGIN |
| 227 | + |
| 228 | + --prepare global temp table with sources |
| 229 | + populate_tmp_table(a_coverage_options); |
| 230 | + |
| 231 | + l_source_objects_crsr := ut_coverage_helper.get_tmp_table_objects_cursor(); |
| 232 | + LOOP |
| 233 | + FETCH l_source_objects_crsr |
| 234 | + INTO l_source_object; |
| 235 | + EXIT WHEN l_source_objects_crsr%NOTFOUND; |
| 236 | + |
| 237 | + --get coverage data |
| 238 | + l_line_calls := get_raw_coverage_data(l_source_object.owner, l_source_object.name); |
| 239 | + |
| 240 | + --if there is coverage, we need to filter out the garbage (badly indicated data from dbms_profiler) |
| 241 | + IF l_line_calls.count > 0 THEN |
| 242 | + --remove lines that should not be indicted as meaningful |
| 243 | + FOR i IN 1 .. l_source_object.to_be_skipped_list.count |
| 244 | + LOOP |
| 245 | + IF l_source_object.to_be_skipped_list(i) IS NOT NULL THEN |
| 246 | + l_line_calls.delete(l_source_object.to_be_skipped_list(i)); |
| 247 | + END IF; |
| 248 | + END LOOP; |
| 249 | + END IF; |
| 250 | + |
| 251 | + --if there are no file mappings or object was actually captured by profiler |
| 252 | + IF a_coverage_options.file_mappings IS NULL OR l_line_calls.count > 0 THEN |
| 253 | + |
| 254 | + --populate total stats |
| 255 | + l_result.total_lines := l_result.total_lines + l_source_object.lines_count; |
| 256 | + |
| 257 | + --populate object level coverage stats |
| 258 | + IF NOT l_result.objects.exists(l_source_object.full_name) THEN |
| 259 | + l_result.objects(l_source_object.full_name) := l_new_unit; |
| 260 | + l_result.objects(l_source_object.full_name).owner := l_source_object.owner; |
| 261 | + l_result.objects(l_source_object.full_name).name := l_source_object.name; |
| 262 | + l_result.objects(l_source_object.full_name).total_lines := l_source_object.lines_count; |
| 263 | + END IF; |
| 264 | + --map to results |
| 265 | + line_no := l_line_calls.first; |
| 266 | + IF line_no IS NULL THEN |
| 267 | + l_result.uncovered_lines := l_result.uncovered_lines + |
| 268 | + l_source_object.lines_count; |
| 269 | + l_result.objects(l_source_object.full_name).uncovered_lines := l_source_object.lines_count; |
| 270 | + ELSE |
| 271 | + LOOP |
| 272 | + EXIT WHEN line_no IS NULL; |
| 273 | + |
| 274 | + --total stats |
| 275 | + |
| 276 | + --Get total blocks ,blocks covered, blocks not covered |
| 277 | + l_result.total_blocks := NVL(l_result.total_blocks, 0) + l_line_calls(line_no) |
| 278 | + .blocks; |
| 279 | + l_result.covered_blocks := NVL(l_result.covered_blocks, 0) + l_line_calls(line_no) |
| 280 | + .covered_blocks; |
| 281 | + l_result.uncovered_blocks := NVL(l_result.uncovered_blocks, 0) + |
| 282 | + (l_line_calls(line_no).blocks - l_line_calls(line_no) |
| 283 | + .covered_blocks); |
| 284 | + |
| 285 | + --If line is not partially covered add as full line cover |
| 286 | + IF l_line_calls(line_no).partcovered = 1 THEN |
| 287 | + l_result.partcovered_lines := l_result.partcovered_lines + 1; |
| 288 | + ELSE |
| 289 | + l_result.covered_lines := l_result.covered_lines + 1; |
| 290 | + END IF; |
| 291 | + |
| 292 | + -- Use nvl as be default is null |
| 293 | + --Increase total blocks |
| 294 | + l_result.objects(l_source_object.full_name).total_blocks := NVL(l_result.objects(l_source_object.full_name) |
| 295 | + .total_blocks, |
| 296 | + 0) + l_line_calls(line_no) |
| 297 | + .blocks; |
| 298 | + |
| 299 | + --Total uncovered blocks is a line blocks minus covered blocsk |
| 300 | + l_result.objects(l_source_object.full_name).uncovered_blocks := NVL(l_result.objects(l_source_object.full_name) |
| 301 | + .uncovered_blocks, |
| 302 | + 0) + |
| 303 | + (l_line_calls(line_no) |
| 304 | + .blocks - l_line_calls(line_no) |
| 305 | + .covered_blocks); |
| 306 | + |
| 307 | + --If we have any coverted blocks in line |
| 308 | + IF l_line_calls(line_no).covered_blocks > 0 THEN |
| 309 | + |
| 310 | + l_result.executions := l_result.executions + 1; |
| 311 | + --object level stats |
| 312 | + |
| 313 | + IF l_line_calls(line_no).partcovered = 1 THEN |
| 314 | + l_result.objects(l_source_object.full_name).partcovered_lines := l_result.objects(l_source_object.full_name) |
| 315 | + .partcovered_lines + 1; |
| 316 | + |
| 317 | + ELSE |
| 318 | + l_result.objects(l_source_object.full_name).covered_lines := l_result.objects(l_source_object.full_name) |
| 319 | + .covered_lines + 1; |
| 320 | + |
| 321 | + END IF; |
| 322 | + |
| 323 | + l_result.objects(l_source_object.full_name).covered_blocks := NVL(l_result.objects(l_source_object.full_name) |
| 324 | + .covered_blocks, |
| 325 | + 0) + l_line_calls(line_no) |
| 326 | + .covered_blocks; |
| 327 | + |
| 328 | + l_result.objects(l_source_object.full_name).executions := NVL(l_result.objects(l_source_object.full_name) |
| 329 | + .executions, |
| 330 | + 0) + 1; |
| 331 | + l_result.objects(l_source_object.full_name).lines(line_no).execution := 1; |
| 332 | + |
| 333 | + ELSIF l_line_calls(line_no).covered_blocks = 0 THEN |
| 334 | + l_result.uncovered_lines := l_result.uncovered_lines + 1; |
| 335 | + l_result.objects(l_source_object.full_name).uncovered_lines := l_result.objects(l_source_object.full_name) |
| 336 | + .uncovered_lines + 1; |
| 337 | + l_result.objects(l_source_object.full_name).lines(line_no).execution := 0; |
| 338 | + |
| 339 | + END IF; |
| 340 | + |
| 341 | + l_result.objects(l_source_object.full_name).lines(line_no).partcove := l_line_calls(line_no) |
| 342 | + .partcovered; |
| 343 | + |
| 344 | + line_no := l_line_calls.next(line_no); |
| 345 | + END LOOP; |
| 346 | + END IF; |
| 347 | + END IF; |
| 348 | + |
| 349 | + END LOOP; |
| 350 | + |
| 351 | + CLOSE l_source_objects_crsr; |
| 352 | + |
| 353 | + RETURN l_result; |
| 354 | + END get_coverage_data; |
| 355 | + |
| 356 | +END; |
| 357 | +/ |
0 commit comments