diff --git a/docs/userguide/coverage.md b/docs/userguide/coverage.md index 38efc237e..20fcbc9cb 100644 --- a/docs/userguide/coverage.md +++ b/docs/userguide/coverage.md @@ -53,6 +53,40 @@ The report allow you to navigate to each source file and inspect line by line co ![Coverage Details page](../images/coverage_html_details.png) +#### Oracle 12.2 block coverage. +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). +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'). + +Example: +```sql +begin + ut.run(ut_coverage_html_reporter(),a_coverage_type => 'block'); +end; +/ +``` + +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. + +#### Oracle 12.2 extended coverage with profiler and block coverage +Using data collected from profiler and block coverage running parallel we are able to enrich information about coverage. +For every line recorded by profiler if we have a partially covered same line in block coverage we will display that information +presenting line as partially covered and displaying number of block and how many blocks been covered in that line. + +Example: +```sql +begin + ut.run(ut_coverage_html_reporter(),a_coverage_type => 'extended'); +end; +/ +``` + +Sample output: +![Package Coverage Summary](../images/extended_coverage_html_summary.png) + +![Line Coverage Details](../images/extended_coverage_html_line.png) + + + ### Coverage reporting options There are two distinct ways to gather code coverage: diff --git a/source/api/ut.pkb b/source/api/ut.pkb index b61746762..4225629b8 100644 --- a/source/api/ut.pkb +++ b/source/api/ut.pkb @@ -113,8 +113,8 @@ create or replace package body ut is begin ut_runner.run( a_paths, ut_reporters(coalesce(a_reporter,ut_documentation_reporter())), - ut_utils.int_to_boolean(a_color_console), a_coverage_schemes, - a_source_file_mappings, a_test_file_mappings, a_include_objects, a_exclude_objects + ut_utils.int_to_boolean(a_color_console), a_coverage_schemes, a_source_file_mappings, + a_test_file_mappings, a_include_objects, a_exclude_objects, false ); rollback; end; @@ -131,7 +131,7 @@ create or replace package body ut is ut_utils.int_to_boolean(a_color_console), a_coverage_schemes, ut_file_mapper.build_file_mappings(a_source_files), ut_file_mapper.build_file_mappings(a_test_files), - a_include_objects, a_exclude_objects + a_include_objects, a_exclude_objects, false ); rollback; end; @@ -376,7 +376,7 @@ create or replace package body ut is begin ut.run( l_paths, a_reporter, a_color_console, a_coverage_schemes, a_source_files, a_test_files, - a_include_objects, a_exclude_objects + a_include_objects, a_exclude_objects ); end; diff --git a/source/api/ut_runner.pks b/source/api/ut_runner.pks index 224b5e766..ae83b2e60 100644 --- a/source/api/ut_runner.pks +++ b/source/api/ut_runner.pks @@ -58,7 +58,8 @@ create or replace package ut_runner authid current_user is procedure run( a_paths ut_varchar2_list, a_reporters ut_reporters, a_color_console boolean := false, a_coverage_schemes ut_varchar2_list := null, a_source_file_mappings ut_file_mappings := null, a_test_file_mappings ut_file_mappings := null, - a_include_objects ut_varchar2_list := null, a_exclude_objects ut_varchar2_list := null, a_fail_on_errors boolean default false + a_include_objects ut_varchar2_list := null, a_exclude_objects ut_varchar2_list := null, + a_fail_on_errors boolean default false ); /** diff --git a/source/core/coverage/dbms_plssqlcode.sql b/source/core/coverage/dbms_plssqlcode.sql new file mode 100644 index 000000000..e3aa2ec9a --- /dev/null +++ b/source/core/coverage/dbms_plssqlcode.sql @@ -0,0 +1,8 @@ +begin + $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then + dbms_plsql_code_coverage.create_coverage_tables(force_it => true); + $else + null; + $end +end; +/ diff --git a/source/core/coverage/ut_coverage.pkb b/source/core/coverage/ut_coverage.pkb index 80e4f3829..b56c82ccd 100644 --- a/source/core/coverage/ut_coverage.pkb +++ b/source/core/coverage/ut_coverage.pkb @@ -16,21 +16,10 @@ create or replace package body ut_coverage is limitations under the License. */ + type t_source_lines is table of binary_integer; - -- The source query has two important transformations done in it. - -- the flag: to_be_skipped ='Y' is set for a line of code that is badly reported by DBMS_PROFILER as executed 0 times. - -- This includes lines that are: - -- - PACKAGE, PROCEDURE, FUNCTION definition line, - -- - BEGIN, END of a block - -- Another transformation is adjustment of line number for TRIGGER body. - -- DBMS_PROFILER is reporting line numbers for triggers not as defined in DBA_SOURCE, its usign line numbers as defined in DBA_TRIGGERS - -- the DBA_TRIGGERS does not contain the trigger specification lines, only lines that define the trigger body. - -- the query adjusts the line numbers for triggers by finding first occurrence of begin|declare|compound in the trigger body line. - -- The subquery is optimized by: - -- - COALESCE function -> it will execute only for TRIGGERS - -- - scalar subquery cache -> it will only execute once for one trigger source code. - function get_cov_sources_sql(a_coverage_options ut_coverage_options) return varchar2 is + function get_cov_sources_sql(a_coverage_options ut_coverage_options, a_skipped_lines varchar2 default 'Y') return varchar2 is l_result varchar2(32767); l_full_name varchar2(100); l_view_name varchar2(200) := ut_metadata.get_dba_view('dba_source'); @@ -54,8 +43,10 @@ create or replace package body ut_coverage is where t.owner = s.owner and t.type = s.type and t.name = s.name and regexp_like( t.text, '[A-Za-z0-9$#_]*(begin|declare|compound).*','i')) ) as line, - s.text, - case + s.text, ]'; + if a_skipped_lines = 'Y' then + l_result := l_result || + q'[case when -- to avoid execution of regexp_like on every line -- first do a rough check for existence of search pattern keyword @@ -70,8 +61,13 @@ create or replace package body ut_coverage is '^([\t ]*(((not)?\s*(overriding|final|instantiable)[\t ]*)*(static|constructor|member)?[\t ]*(procedure|function)|package([\t ]+body)|begin|end([\t ]+\S+)*[ \t]*;))', 'i' ) then 'Y' - end as to_be_skipped - from ]'||l_view_name||q'[ s]'; + end as to_be_skipped ]'; + else + l_result := l_result || q'['N' as to_be_skipped ]'; + end if; + + l_result := l_result ||' from '||l_view_name||q'[ s]'; + if a_coverage_options.file_mappings is not empty then l_result := l_result || ' join table(:file_mappings) f @@ -95,7 +91,7 @@ create or replace package body ut_coverage is return l_result; end; - function get_cov_sources_cursor(a_coverage_options ut_coverage_options) return sys_refcursor is + function get_cov_sources_cursor(a_coverage_options in ut_coverage_options,a_sql in varchar2) return sys_refcursor is l_cursor sys_refcursor; l_skip_objects ut_object_names; l_sql varchar2(32767); @@ -104,7 +100,7 @@ create or replace package body ut_coverage is --skip all the utplsql framework objects and all the unit test packages that could potentially be reported by coverage. l_skip_objects := ut_utils.get_utplsql_objects_list() multiset union all coalesce(a_coverage_options.exclude_objects, ut_object_names()); end if; - l_sql := get_cov_sources_sql(a_coverage_options); + l_sql := a_sql; if a_coverage_options.file_mappings is not empty then open l_cursor for l_sql using a_coverage_options.file_mappings, l_skip_objects; elsif a_coverage_options.include_objects is not empty then @@ -115,7 +111,7 @@ create or replace package body ut_coverage is return l_cursor; end; - procedure populate_tmp_table(a_coverage_options ut_coverage_options) is + procedure populate_tmp_table(a_coverage_options in ut_coverage_options,a_sql in varchar2) is pragma autonomous_transaction; l_cov_sources_crsr sys_refcursor; l_cov_sources_data ut_coverage_helper.t_coverage_sources_tmp_rows; @@ -124,7 +120,7 @@ create or replace package body ut_coverage is if not ut_coverage_helper.is_tmp_table_populated() or ut_coverage_helper.is_develop_mode() then ut_coverage_helper.cleanup_tmp_table(); - l_cov_sources_crsr := get_cov_sources_cursor(a_coverage_options); + l_cov_sources_crsr := get_cov_sources_cursor(a_coverage_options,a_sql); loop 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 /** * Public functions */ - procedure coverage_start is + procedure coverage_start(a_coverage_options ut_coverage_options default null) is begin ut_coverage_helper.coverage_start('utPLSQL Code coverage run '||ut_utils.to_string(systimestamp)); end; - procedure coverage_start_develop is + procedure coverage_start_develop(a_coverage_options ut_coverage_options default null) is begin - ut_coverage_helper.coverage_start_develop(); + ut_coverage_helper.coverage_start_develop; end; procedure coverage_pause is @@ -174,81 +170,54 @@ create or replace package body ut_coverage is end; function get_coverage_data(a_coverage_options ut_coverage_options) return t_coverage is - l_line_calls ut_coverage_helper.t_unit_line_calls; - l_result t_coverage; - l_new_unit t_unit_coverage; - l_line_no binary_integer; - l_source_objects_crsr ut_coverage_helper.t_tmp_table_objects_crsr; - l_source_object ut_coverage_helper.t_tmp_table_object; + l_result_block ut_coverage.t_coverage; + l_result_profiler_enrich ut_coverage.t_coverage; + l_object ut_coverage.t_full_name; + l_line_no binary_integer; begin - - --prepare global temp table with sources - populate_tmp_table(a_coverage_options); - - l_source_objects_crsr := ut_coverage_helper.get_tmp_table_objects_cursor(); - loop - fetch l_source_objects_crsr into l_source_object; - exit when l_source_objects_crsr%notfound; - - --get coverage data - l_line_calls := ut_coverage_helper.get_raw_coverage_data( l_source_object.owner, l_source_object.name ); - - --if there is coverage, we need to filter out the garbage (badly indicated data from dbms_profiler) - if l_line_calls.count > 0 then - --remove lines that should not be indicted as meaningful - for i in 1 .. l_source_object.to_be_skipped_list.count loop - if l_source_object.to_be_skipped_list(i) is not null then - l_line_calls.delete(l_source_object.to_be_skipped_list(i)); - end if; - end loop; - end if; - - --if there are no file mappings or object was actually captured by profiler - if a_coverage_options.file_mappings is null or l_line_calls.count > 0 then - - --populate total stats - l_result.total_lines := l_result.total_lines + l_source_object.lines_count; - - --populate object level coverage stats - if not l_result.objects.exists(l_source_object.full_name) then - l_result.objects(l_source_object.full_name) := l_new_unit; - l_result.objects(l_source_object.full_name).owner := l_source_object.owner; - l_result.objects(l_source_object.full_name).name := l_source_object.name; - l_result.objects(l_source_object.full_name).total_lines := l_source_object.lines_count; + -- Get raw data for both reporters, order is important as tmp table will skip headers and dont populate + -- tmp table for block again. + l_result_profiler_enrich:= ut_coverage_profiler.get_coverage_data(a_coverage_options => a_coverage_options); + + -- If block coverage available we will use it. + $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then + l_result_block := ut_coverage_block.get_coverage_data(a_coverage_options => a_coverage_options); + + -- Enrich profiler results with some of the block results + l_object := l_result_profiler_enrich.objects.first; + while (l_object is not null) + loop + + l_line_no := l_result_profiler_enrich.objects(l_object).lines.first; + + -- to avoid no data found check if we got object in profiler + if l_result_block.objects.exists(l_object) then + while (l_line_no is not null) + loop + -- To avoid no data check for object line + if l_result_block.objects(l_object).lines.exists(l_line_no) then + -- enrich line level stats + l_result_profiler_enrich.objects(l_object).lines(l_line_no).partcove := l_result_block.objects(l_object).lines(l_line_no).partcove; + 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; + 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; + -- enrich object level stats + 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; end if; - --map to results - l_line_no := l_line_calls.first; - if l_line_no is null then - l_result.uncovered_lines := l_result.uncovered_lines + l_source_object.lines_count; - l_result.objects(l_source_object.full_name).uncovered_lines := l_source_object.lines_count; - else - loop - exit when l_line_no is null; - - if l_line_calls(l_line_no) > 0 then - --total stats - l_result.covered_lines := l_result.covered_lines + 1; - l_result.executions := l_result.executions + l_line_calls(l_line_no); - --object level stats - l_result.objects(l_source_object.full_name).covered_lines := l_result.objects(l_source_object.full_name).covered_lines + 1; - l_result.objects(l_source_object.full_name).executions := l_result.objects(l_source_object.full_name).executions + l_line_calls(l_line_no); - elsif l_line_calls(l_line_no) = 0 then - l_result.uncovered_lines := l_result.uncovered_lines + 1; - l_result.objects(l_source_object.full_name).uncovered_lines := l_result.objects(l_source_object.full_name).uncovered_lines + 1; - end if; - l_result.objects(l_source_object.full_name).lines(l_line_no) := l_line_calls(l_line_no); - - l_line_no := l_line_calls.next(l_line_no); - end loop; - end if; - end if; - - end loop; - - close l_source_objects_crsr; - - return l_result; - end get_coverage_data; - + --At the end go to next line + l_line_no := l_result_profiler_enrich.objects(l_object).lines.next(l_line_no); + end loop; + --total level stats enrich + l_result_profiler_enrich.partcovered_lines := nvl(l_result_profiler_enrich.partcovered_lines,0) + l_result_profiler_enrich.objects(l_object).partcovered_lines; + -- At the end go to next object + end if; + + l_object := l_result_profiler_enrich.objects.next(l_object); + + end loop; + $end + + return l_result_profiler_enrich; + end get_coverage_data; + end; / diff --git a/source/core/coverage/ut_coverage.pks b/source/core/coverage/ut_coverage.pks index 68f49d86a..2dff5ddc1 100644 --- a/source/core/coverage/ut_coverage.pks +++ b/source/core/coverage/ut_coverage.pks @@ -15,43 +15,64 @@ create or replace package ut_coverage authid current_user is See the License for the specific language governing permissions and limitations under the License. */ - + + gc_proftab_coverage constant varchar2(32) := 'proftab'; + gc_block_coverage constant varchar2(32) := 'block'; + gc_extended_coverage constant varchar2(32) := 'extended'; + -- total run coverage information - subtype t_full_name is varchar2(4000); + subtype t_full_name is varchar2(4000); subtype t_object_name is varchar2(250); - subtype t_line_executions is binary_integer; + --subtype t_line_executions is binary_integer; + + type t_line_executions is record( + executions binary_integer + ,partcove binary_integer + ,no_blocks binary_integer + ,covered_blocks binary_integer); -- line coverage information indexed by line no. + --type tt_lines is table of t_line_executions index by binary_integer; type tt_lines is table of t_line_executions index by binary_integer; --unit coverage information record - type t_unit_coverage is record ( - owner varchar2(128), - name varchar2(128), - covered_lines binary_integer := 0, - uncovered_lines binary_integer := 0, - total_lines binary_integer := 0, - executions number(38,0) := 0, - lines tt_lines - ); + type t_unit_coverage is record( + owner varchar2(128) + ,name varchar2(128) + ,covered_lines binary_integer := 0 + ,uncovered_lines binary_integer := 0 + ,partcovered_lines binary_integer := 0 + ,total_blocks binary_integer default null + ,covered_blocks binary_integer default null + ,uncovered_blocks binary_integer default null + ,total_lines binary_integer := 0 + ,executions number(38, 0) := 0 + ,lines tt_lines); -- coverage information indexed by full object name (schema.object) type tt_program_units is table of t_unit_coverage index by t_full_name; -- total run coverage information type t_coverage is record( - covered_lines binary_integer := 0, - uncovered_lines binary_integer := 0, - total_lines binary_integer := 0, - executions number(38,0) := 0, - objects tt_program_units - ); - - procedure coverage_start; + covered_lines binary_integer := 0 + ,uncovered_lines binary_integer := 0 + ,partcovered_lines binary_integer := 0 + ,total_lines binary_integer default null + ,total_blocks binary_integer default null + ,covered_blocks binary_integer default null + ,uncovered_blocks binary_integer default null + ,executions number(38, 0) := 0 + ,objects tt_program_units); + + function get_cov_sources_sql(a_coverage_options ut_coverage_options, a_skipped_lines varchar2 default 'Y') return varchar2; + + procedure populate_tmp_table(a_coverage_options ut_coverage_options, a_sql in varchar2); + + procedure coverage_start(a_coverage_options ut_coverage_options default null); /* * Start coverage in develop mode, where all internal calls to utPLSQL itself are also included */ - procedure coverage_start_develop; + procedure coverage_start_develop(a_coverage_options ut_coverage_options default null); procedure coverage_stop; diff --git a/source/core/coverage/ut_coverage_block.pkb b/source/core/coverage/ut_coverage_block.pkb new file mode 100644 index 000000000..c405fd262 --- /dev/null +++ b/source/core/coverage/ut_coverage_block.pkb @@ -0,0 +1,160 @@ +create or replace package body ut_coverage_block is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + + type t_source_lines is table of binary_integer; + + /** + * Public functions + */ + + function get_coverage_data(a_coverage_options ut_coverage_options) return ut_coverage.t_coverage is + l_line_calls ut_coverage_helper.t_unit_line_calls; + l_result ut_coverage.t_coverage; + l_new_unit ut_coverage.t_unit_coverage; + l_line_no binary_integer; + l_source_objects_crsr ut_coverage_helper.t_tmp_table_objects_crsr; + l_source_object ut_coverage_helper.t_tmp_table_object; + begin + --prepare global temp table with sources + ut_coverage.populate_tmp_table(a_coverage_options,ut_coverage.get_cov_sources_sql(a_coverage_options,'N')); + + l_source_objects_crsr := ut_coverage_helper.get_tmp_table_objects_cursor(); + loop + fetch l_source_objects_crsr + into l_source_object; + exit when l_source_objects_crsr%notfound; + + --get coverage data + l_line_calls := ut_coverage_helper_block.get_raw_coverage_data(l_source_object.owner, l_source_object.name); + --if there is coverage, we need to filter out the garbage (badly indicated data) + if l_line_calls.count > 0 then + --remove lines that should not be indicted as meaningful + for i in 1 .. l_source_object.to_be_skipped_list.count loop + if l_source_object.to_be_skipped_list(i) is not null then + l_line_calls.delete(l_source_object.to_be_skipped_list(i)); + end if; + end loop; + end if; + + --if there are no file mappings or object was actually captured by profiler + if a_coverage_options.file_mappings is null or l_line_calls.count > 0 then + + --populate total stats + l_result.total_lines := nvl(l_result.total_lines,0) + l_source_object.lines_count; + + --populate object level coverage stats + if not l_result.objects.exists(l_source_object.full_name) then + l_result.objects(l_source_object.full_name) := l_new_unit; + l_result.objects(l_source_object.full_name).owner := l_source_object.owner; + l_result.objects(l_source_object.full_name).name := l_source_object.name; + l_result.objects(l_source_object.full_name).total_lines := l_source_object.lines_count; + end if; + --map to results + l_line_no := l_line_calls.first; + if l_line_no is null then + l_result.uncovered_lines := l_result.uncovered_lines + l_source_object.lines_count; + l_result.objects(l_source_object.full_name).uncovered_lines := l_source_object.lines_count; + else + loop + exit when l_line_no is null; + + --turn the block coverage into a line coverage format to allow for reading. + --whenever the linst is a part covered treat that line as a hit and execution but only part covered + + --total stats + --Get total blocks ,blocks covered, blocks not covered this will be used for PCT calc + l_result.total_blocks := nvl(l_result.total_blocks, 0) + l_line_calls(l_line_no).blocks; + l_result.covered_blocks := nvl(l_result.covered_blocks, 0) + l_line_calls(l_line_no).covered_blocks; + l_result.uncovered_blocks := nvl(l_result.uncovered_blocks, 0) + + (l_line_calls(l_line_no).blocks - l_line_calls(l_line_no).covered_blocks); + + --If line is partially covered add as part line cover and covered for line reporter + if l_line_calls(l_line_no).partcovered = 1 then + l_result.partcovered_lines := l_result.partcovered_lines + 1; + end if; + + if l_line_calls(l_line_no).covered_blocks > 0 then + l_result.covered_lines := l_result.covered_lines + 1; + end if; + + -- Use nvl as be default is null and screw the calcs + --Increase total blocks + l_result.objects(l_source_object.full_name).lines(l_line_no).no_blocks := l_line_calls(l_line_no).blocks; + l_result.objects(l_source_object.full_name).lines(l_line_no).covered_blocks := l_line_calls(l_line_no).covered_blocks; + l_result.objects(l_source_object.full_name).total_blocks := nvl(l_result.objects(l_source_object.full_name) + .total_blocks + ,0) + l_line_calls(l_line_no).blocks; + + --Total uncovered blocks is a line blocks minus covered blocsk + l_result.objects(l_source_object.full_name).uncovered_blocks := nvl(l_result.objects(l_source_object.full_name) + .uncovered_blocks + ,0) + + (l_line_calls(l_line_no).blocks - l_line_calls(l_line_no) + .covered_blocks); + + --If we have any covered blocks in line + if l_line_calls(l_line_no).covered_blocks > 0 then + --If any block is covered then we have a hit on that line + l_result.executions := l_result.executions + 1; + --object level stats + --If its part covered then mark it else treat as full cov + if l_line_calls(l_line_no).partcovered = 1 then + l_result.objects(l_source_object.full_name).partcovered_lines := l_result.objects(l_source_object.full_name) + .partcovered_lines + 1; + end if; + l_result.objects(l_source_object.full_name).covered_lines := l_result.objects(l_source_object.full_name) + .covered_lines + 1; + + --How many blocks we covered + l_result.objects(l_source_object.full_name).covered_blocks := nvl(l_result.objects(l_source_object.full_name) + .covered_blocks + ,0) + l_line_calls(l_line_no) + .covered_blocks; + + --Object line executions + l_result.objects(l_source_object.full_name).executions := nvl(l_result.objects(l_source_object.full_name) + .executions + ,0) + 1; + + l_result.objects(l_source_object.full_name).lines(l_line_no).executions := 1; + + --Whenever there is no covered block treat as uncovered (query returns only lines where the blocks are in code so we + --dont have a false results here when there is no blocks + elsif l_line_calls(l_line_no).covered_blocks = 0 then + l_result.uncovered_lines := l_result.uncovered_lines + 1; + l_result.objects(l_source_object.full_name).uncovered_lines := l_result.objects(l_source_object.full_name) + .uncovered_lines + 1; + l_result.objects(l_source_object.full_name).lines(l_line_no).executions := 0; + end if; + --increase part covered counter (+ 1/0) + l_result.objects(l_source_object.full_name).lines(l_line_no).partcove := l_line_calls(l_line_no).partcovered; + l_line_no := l_line_calls.next(l_line_no); + end loop; + end if; + end if; + + end loop; + + close l_source_objects_crsr; + + return l_result; + end get_coverage_data; + +end; +/ diff --git a/source/core/coverage/ut_coverage_block.pks b/source/core/coverage/ut_coverage_block.pks new file mode 100644 index 000000000..00c5e9e28 --- /dev/null +++ b/source/core/coverage/ut_coverage_block.pks @@ -0,0 +1,22 @@ +create or replace package ut_coverage_block authid current_user is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + function get_coverage_data(a_coverage_options ut_coverage_options) return ut_coverage.t_coverage; + +end; +/ diff --git a/source/core/coverage/ut_coverage_helper.pkb b/source/core/coverage/ut_coverage_helper.pkb index b4bd79705..41f5108d5 100644 --- a/source/core/coverage/ut_coverage_helper.pkb +++ b/source/core/coverage/ut_coverage_helper.pkb @@ -16,20 +16,57 @@ create or replace package body ut_coverage_helper is limitations under the License. */ - g_coverage_id integer; + g_develop_mode boolean not null := false; g_is_started boolean not null := false; + + type t_proftab_row is record ( + line binary_integer, + calls number(38,0) + ); + + type t_proftab_rows is table of t_proftab_row; + + type t_block_row is record( + line binary_integer + ,blocks binary_integer + ,covered_blocks binary_integer); + + type t_block_rows is table of t_block_row; + + procedure set_coverage_status(a_started in boolean) is + begin + g_is_started := a_started; + end; + + procedure set_develop_mode(a_develop_mode in boolean) is + begin + g_develop_mode := a_develop_mode; + end; + + function get_coverage_id(a_coverage_type in varchar2) return integer is + begin + return g_coverage_id(a_coverage_type); + end; + function is_develop_mode return boolean is begin return g_develop_mode; end; procedure coverage_start_internal(a_run_comment varchar2) is - begin - dbms_profiler.start_profiler(run_comment => a_run_comment, run_number => g_coverage_id); + begin + $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then + ut_coverage_helper_block.coverage_start(a_run_comment => a_run_comment ,a_coverage_id => g_coverage_id(ut_coverage.gc_block_coverage) ); + ut_coverage_helper_profiler.coverage_start(a_run_comment => a_run_comment, a_coverage_id => g_coverage_id(ut_coverage.gc_proftab_coverage)); + coverage_pause(); + $else + ut_coverage_helper_profiler.coverage_start(a_run_comment => a_run_comment, a_coverage_id => g_coverage_id(ut_coverage.gc_proftab_coverage)); + coverage_pause(); + $end + g_is_started := true; - coverage_pause(); end; procedure coverage_start(a_run_comment varchar2) is @@ -49,24 +86,27 @@ create or replace package body ut_coverage_helper is end; procedure coverage_pause is - l_return_code binary_integer; begin if not g_develop_mode then - l_return_code := dbms_profiler.pause_profiler(); + ut_coverage_helper_profiler.coverage_pause(); end if; end; procedure coverage_resume is - l_return_code binary_integer; begin - l_return_code := dbms_profiler.resume_profiler(); + ut_coverage_helper_profiler.coverage_resume(); end; procedure coverage_stop is begin if not g_develop_mode then g_is_started := false; - dbms_profiler.stop_profiler(); + $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then + ut_coverage_helper_profiler.coverage_stop(); + ut_coverage_helper_block.coverage_stop(); + $else + ut_coverage_helper_profiler.coverage_stop(); + $end end if; end; @@ -74,49 +114,29 @@ create or replace package body ut_coverage_helper is begin g_develop_mode := false; g_is_started := false; - dbms_profiler.stop_profiler(); + $if dbms_db_version.version = 12 and dbms_db_version.release >= 2 or dbms_db_version.version > 12 $then + ut_coverage_helper_profiler.coverage_stop(); + ut_coverage_helper_block.coverage_stop(); + $else + ut_coverage_helper_profiler.coverage_stop(); + $end + end; - function get_raw_coverage_data(a_object_owner varchar2, a_object_name varchar2) return t_unit_line_calls is - type coverage_row is record ( - line binary_integer, - calls number(38,0) - ); - type coverage_rows is table of coverage_row; - l_tmp_data coverage_rows; - l_results t_unit_line_calls; - begin - select d.line#, - -- This transformation addresses two issues: - -- 1. dbms_profiler shows multiple unit_number for single code unit; - -- to address this, we take a sum od all units by name - -- 2. some lines show 0 total_occur while they were executed (time > 0) - -- in this case we show 1 to indicate that there was execution even if we don't know how many there were - case when sum(d.total_occur) = 0 and sum(d.total_time) > 0 then 1 else sum(d.total_occur) end total_occur - bulk collect into l_tmp_data - from plsql_profiler_units u - join plsql_profiler_data d - on u.runid = d.runid - and u.unit_number = d.unit_number - where u.runid = g_coverage_id - and u.unit_owner = a_object_owner - and u.unit_name = a_object_name - --exclude specification - and u.unit_type not in ('PACKAGE SPEC', 'TYPE SPEC', 'ANONYMOUS BLOCK') - group by d.line#; - for i in 1 .. l_tmp_data.count loop - l_results(l_tmp_data(i).line) := l_tmp_data(i).calls; - end loop; - return l_results; - end; - - procedure mock_coverage_id(a_coverage_id integer) is + procedure mock_coverage_id(a_coverage_id integer,a_coverage_type in varchar2) is begin g_develop_mode := true; g_is_started := true; - g_coverage_id := a_coverage_id; + g_coverage_id(a_coverage_type) := a_coverage_id; end; + procedure mock_coverage_id(a_coverage_id g_coverage_arr) is + begin + g_develop_mode := true; + g_is_started := true; + g_coverage_id := a_coverage_id; + end; + procedure insert_into_tmp_table(a_data t_coverage_sources_tmp_rows) is begin forall i in 1 .. a_data.count diff --git a/source/core/coverage/ut_coverage_helper.pks b/source/core/coverage/ut_coverage_helper.pks index 3bd76a3c5..ee7c33846 100644 --- a/source/core/coverage/ut_coverage_helper.pks +++ b/source/core/coverage/ut_coverage_helper.pks @@ -16,9 +16,28 @@ create or replace package ut_coverage_helper authid definer is limitations under the License. */ + type g_coverage_arr is table of integer index by varchar2(30); + + g_coverage_id g_coverage_arr; + + + function get_coverage_id(a_coverage_type in varchar2) return integer; + + procedure set_coverage_status(a_started in boolean); + + procedure set_develop_mode(a_develop_mode in boolean); + --table of line calls indexed by line number --!!! this table is sparse!!! - type t_unit_line_calls is table of number(38,0) index by binary_integer; + --type t_unit_line_calls is table of number(38,0) index by binary_integer; + + type t_unit_line_call is record( + blocks binary_integer default 0 + ,covered_blocks binary_integer default 0 + ,partcovered binary_integer default 0 + ,calls binary_integer default 0); + + type t_unit_line_calls is table of t_unit_line_call index by binary_integer; type t_coverage_sources_tmp_row is record ( full_name ut_coverage_sources_tmp.full_name%type, @@ -43,7 +62,7 @@ create or replace package ut_coverage_helper authid definer is function is_develop_mode return boolean; - procedure coverage_start(a_run_comment varchar2); + procedure coverage_start(a_run_comment in varchar2); /* * Start coverage in develop mode, where all internal calls to utPLSQL itself are also included @@ -58,13 +77,13 @@ create or replace package ut_coverage_helper authid definer is procedure coverage_resume; - function get_raw_coverage_data(a_object_owner varchar2, a_object_name varchar2) return t_unit_line_calls; - /*** * Allows overwriting of private global variable g_coverage_id * Used internally, only for unit testing of the framework only */ - procedure mock_coverage_id(a_coverage_id integer); + procedure mock_coverage_id(a_coverage_id integer,a_coverage_type in varchar2); + + procedure mock_coverage_id(a_coverage_id g_coverage_arr); procedure insert_into_tmp_table(a_data t_coverage_sources_tmp_rows); diff --git a/source/core/coverage/ut_coverage_helper_block.pkb b/source/core/coverage/ut_coverage_helper_block.pkb new file mode 100644 index 000000000..8d652099d --- /dev/null +++ b/source/core/coverage/ut_coverage_helper_block.pkb @@ -0,0 +1,91 @@ +create or replace package body ut_coverage_helper_block is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + type t_proftab_row is record ( + line binary_integer, + calls number(38,0) + ); + + type t_proftab_rows is table of t_proftab_row; + + type t_block_row is record( + line binary_integer + ,blocks binary_integer + ,covered_blocks binary_integer); + + type t_block_rows is table of t_block_row; + + procedure coverage_start(a_run_comment varchar2,a_coverage_id out integer) is + begin + a_coverage_id := dbms_plsql_code_coverage.start_coverage(run_comment => a_run_comment); + end; + + procedure coverage_stop is + begin + dbms_plsql_code_coverage.stop_coverage(); + end; + + function block_results(a_object_owner varchar2, a_object_name varchar2) return t_block_rows is + l_raw_coverage sys_refcursor; + l_coverage_rows t_block_rows; + l_coverage_id integer := ut_coverage_helper.get_coverage_id(ut_coverage.gc_block_coverage); + begin + + open l_raw_coverage for q'[select ccb.line + ,count(ccb.block) totalblocks + ,sum(ccb.covered) + from dbmspcc_units ccu + left outer join dbmspcc_blocks ccb + on ccu.run_id = ccb.run_id + and ccu.object_id = ccb.object_id + where ccu.run_id = :a_coverage_id + and ccu.owner = :a_object_owner + and ccu.name = :a_object_name + group by ccb.line + order by 1]' using l_coverage_id,a_object_owner,a_object_name; + + fetch l_raw_coverage bulk collect into l_coverage_rows; + close l_raw_coverage; + + return l_coverage_rows; + end; + + function get_raw_coverage_data(a_object_owner varchar2, a_object_name varchar2) return ut_coverage_helper.t_unit_line_calls is + l_tmp_data t_block_rows; + l_results ut_coverage_helper.t_unit_line_calls; + + begin + l_tmp_data := block_results(a_object_owner => a_object_owner, a_object_name => a_object_name); + + for i in 1 .. l_tmp_data.count loop + l_results(l_tmp_data(i).line).blocks := l_tmp_data(i).blocks; + l_results(l_tmp_data(i).line).covered_blocks := l_tmp_data(i).covered_blocks; + l_results(l_tmp_data(i).line).partcovered := case + when (l_tmp_data(i).covered_blocks > 0) and + (l_tmp_data(i).blocks > l_tmp_data(i).covered_blocks) then + 1 + else + 0 + end; + end loop; + return l_results; + end; + + +end; +/ diff --git a/source/core/coverage/ut_coverage_helper_block.pks b/source/core/coverage/ut_coverage_helper_block.pks new file mode 100644 index 000000000..041db26f7 --- /dev/null +++ b/source/core/coverage/ut_coverage_helper_block.pks @@ -0,0 +1,26 @@ +create or replace package ut_coverage_helper_block authid definer is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + procedure coverage_start(a_run_comment in varchar2,a_coverage_id out integer); + + procedure coverage_stop; + + function get_raw_coverage_data(a_object_owner varchar2, a_object_name varchar2) return ut_coverage_helper.t_unit_line_calls; + +end; +/ diff --git a/source/core/coverage/ut_coverage_helper_profiler.pkb b/source/core/coverage/ut_coverage_helper_profiler.pkb new file mode 100644 index 000000000..eb993d9f9 --- /dev/null +++ b/source/core/coverage/ut_coverage_helper_profiler.pkb @@ -0,0 +1,93 @@ +create or replace package body ut_coverage_helper_profiler is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + type t_proftab_row is record ( + line binary_integer, + calls number(38,0) + ); + + type t_proftab_rows is table of t_proftab_row; + + type t_block_row is record( + line binary_integer + ,blocks binary_integer + ,covered_blocks binary_integer); + + type t_block_rows is table of t_block_row; + + + procedure coverage_start(a_run_comment varchar2,a_coverage_id out integer) is + begin + dbms_profiler.start_profiler(run_comment => a_run_comment, run_number => a_coverage_id); + end; + + procedure coverage_pause is + l_return_code binary_integer; + begin + l_return_code := dbms_profiler.pause_profiler(); + end; + + procedure coverage_resume is + l_return_code binary_integer; + begin + l_return_code := dbms_profiler.resume_profiler(); + end; + + procedure coverage_stop is + begin + dbms_profiler.stop_profiler(); + end; + + function proftab_results(a_object_owner varchar2, a_object_name varchar2) return t_proftab_rows is + l_raw_coverage sys_refcursor; + l_coverage_rows t_proftab_rows; + l_coverage_id integer := ut_coverage_helper.get_coverage_id(ut_coverage.gc_proftab_coverage); + begin + open l_raw_coverage for q'[select d.line#, + case when sum(d.total_occur) = 0 and sum(d.total_time) > 0 then 1 else sum(d.total_occur) end total_occur + from plsql_profiler_units u + join plsql_profiler_data d + on u.runid = d.runid + and u.unit_number = d.unit_number + where u.runid = :a_coverage_id + and u.unit_owner = :a_object_owner + and u.unit_name = :a_object_name + and u.unit_type not in ('PACKAGE SPEC', 'TYPE SPEC', 'ANONYMOUS BLOCK') + group by d.line#]' using l_coverage_id,a_object_owner,a_object_name; + + FETCH l_raw_coverage BULK COLLECT + INTO l_coverage_rows; + CLOSE l_raw_coverage; + + RETURN l_coverage_rows; + end; + + function get_raw_coverage_data(a_object_owner varchar2, a_object_name varchar2) return ut_coverage_helper.t_unit_line_calls is + l_tmp_data t_proftab_rows; + l_results ut_coverage_helper.t_unit_line_calls; + begin + l_tmp_data := proftab_results(a_object_owner => a_object_owner, a_object_name => a_object_name); + + for i in 1 .. l_tmp_data.count loop + l_results(l_tmp_data(i).line).calls := l_tmp_data(i).calls; + end loop; + return l_results; + end; + +end; +/ diff --git a/source/core/coverage/ut_coverage_helper_profiler.pks b/source/core/coverage/ut_coverage_helper_profiler.pks new file mode 100644 index 000000000..cfe08df87 --- /dev/null +++ b/source/core/coverage/ut_coverage_helper_profiler.pks @@ -0,0 +1,30 @@ +create or replace package ut_coverage_helper_profiler authid definer is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + procedure coverage_start(a_run_comment in varchar2,a_coverage_id out integer); + + procedure coverage_stop; + + procedure coverage_pause; + + procedure coverage_resume; + + function get_raw_coverage_data(a_object_owner varchar2, a_object_name varchar2) return ut_coverage_helper.t_unit_line_calls; + +end; +/ diff --git a/source/core/coverage/ut_coverage_profiler.pkb b/source/core/coverage/ut_coverage_profiler.pkb new file mode 100644 index 000000000..64c8cf2d0 --- /dev/null +++ b/source/core/coverage/ut_coverage_profiler.pkb @@ -0,0 +1,99 @@ +create or replace package body ut_coverage_profiler is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + /** + * Public functions + */ + function get_coverage_data(a_coverage_options ut_coverage_options) return ut_coverage.t_coverage is + l_line_calls ut_coverage_helper.t_unit_line_calls; + l_result ut_coverage.t_coverage; + l_new_unit ut_coverage.t_unit_coverage; + l_line_no binary_integer; + l_source_objects_crsr ut_coverage_helper.t_tmp_table_objects_crsr; + l_source_object ut_coverage_helper.t_tmp_table_object; + begin + + --prepare global temp table with sources + ut_coverage.populate_tmp_table(a_coverage_options,ut_coverage.get_cov_sources_sql(a_coverage_options,'Y')); + + l_source_objects_crsr := ut_coverage_helper.get_tmp_table_objects_cursor(); + loop + fetch l_source_objects_crsr into l_source_object; + exit when l_source_objects_crsr%notfound; + + --get coverage data + l_line_calls := ut_coverage_helper_profiler.get_raw_coverage_data( l_source_object.owner, l_source_object.name); + + --if there is coverage, we need to filter out the garbage (badly indicated data from dbms_profiler) + if l_line_calls.count > 0 then + --remove lines that should not be indicted as meaningful + for i in 1 .. l_source_object.to_be_skipped_list.count loop + if l_source_object.to_be_skipped_list(i) is not null then + l_line_calls.delete(l_source_object.to_be_skipped_list(i)); + end if; + end loop; + end if; + + --if there are no file mappings or object was actually captured by profiler + if a_coverage_options.file_mappings is null or l_line_calls.count > 0 then + + --populate total stats + l_result.total_lines := nvl(l_result.total_lines,0) + l_source_object.lines_count; + --populate object level coverage stats + if not l_result.objects.exists(l_source_object.full_name) then + l_result.objects(l_source_object.full_name) := l_new_unit; + l_result.objects(l_source_object.full_name).owner := l_source_object.owner; + l_result.objects(l_source_object.full_name).name := l_source_object.name; + l_result.objects(l_source_object.full_name).total_lines := l_source_object.lines_count; + end if; + --map to results + l_line_no := l_line_calls.first; + if l_line_no is null then + l_result.uncovered_lines := l_result.uncovered_lines + l_source_object.lines_count; + l_result.objects(l_source_object.full_name).uncovered_lines := l_source_object.lines_count; + else + loop + exit when l_line_no is null; + + if l_line_calls(l_line_no).calls > 0 then + --total stats + l_result.covered_lines := l_result.covered_lines + 1; + l_result.executions := l_result.executions + l_line_calls(l_line_no).calls; + --object level stats + l_result.objects(l_source_object.full_name).covered_lines := l_result.objects(l_source_object.full_name).covered_lines + 1; + l_result.objects(l_source_object.full_name).executions := l_result.objects(l_source_object.full_name).executions + l_line_calls(l_line_no).calls; + elsif l_line_calls(l_line_no).calls = 0 then + l_result.uncovered_lines := l_result.uncovered_lines + 1; + l_result.objects(l_source_object.full_name).uncovered_lines := l_result.objects(l_source_object.full_name).uncovered_lines + 1; + end if; + l_result.objects(l_source_object.full_name).lines(l_line_no).executions := l_line_calls(l_line_no).calls; + + l_line_no := l_line_calls.next(l_line_no); + end loop; + end if; + end if; + + end loop; + + close l_source_objects_crsr; + + return l_result; + end get_coverage_data; + +end; +/ diff --git a/source/core/coverage/ut_coverage_profiler.pks b/source/core/coverage/ut_coverage_profiler.pks new file mode 100644 index 000000000..a86d31486 --- /dev/null +++ b/source/core/coverage/ut_coverage_profiler.pks @@ -0,0 +1,22 @@ +create or replace package ut_coverage_profiler authid current_user is + /* + utPLSQL - Version 3 + Copyright 2016 - 2017 utPLSQL Project + + Licensed under the Apache License, Version 2.0 (the "License"): + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + function get_coverage_data(a_coverage_options ut_coverage_options) return ut_coverage.t_coverage; + +end; +/ diff --git a/source/core/coverage/ut_coverage_reporter_base.tpb b/source/core/coverage/ut_coverage_reporter_base.tpb index cf8135ba5..7cde025e2 100644 --- a/source/core/coverage/ut_coverage_reporter_base.tpb +++ b/source/core/coverage/ut_coverage_reporter_base.tpb @@ -19,7 +19,7 @@ create or replace type body ut_coverage_reporter_base is overriding final member procedure before_calling_run(self in out nocopy ut_coverage_reporter_base, a_run ut_run) as begin (self as ut_output_reporter_base).before_calling_run(a_run); - ut_coverage.coverage_start(); + ut_coverage.coverage_start(a_coverage_options => a_run.coverage_options); end; overriding final member procedure before_calling_before_all(self in out nocopy ut_coverage_reporter_base, a_suite in ut_logical_suite) is diff --git a/source/core/types/ut_coverage_options.tps b/source/core/types/ut_coverage_options.tps index 61caffe1b..876457f8e 100644 --- a/source/core/types/ut_coverage_options.tps +++ b/source/core/types/ut_coverage_options.tps @@ -1,4 +1,4 @@ -create or replace type ut_coverage_options as object ( +create or replace type ut_coverage_options force as object ( /* utPLSQL - Version 3 Copyright 2016 - 2017 utPLSQL Project diff --git a/source/core/types/ut_run.tpb b/source/core/types/ut_run.tpb index 446e0ba8e..c7b2056cf 100644 --- a/source/core/types/ut_run.tpb +++ b/source/core/types/ut_run.tpb @@ -15,7 +15,7 @@ create or replace type body ut_run as See the License for the specific language governing permissions and limitations under the License. */ - + constructor function ut_run( self in out nocopy ut_run, a_items ut_suite_items, @@ -29,7 +29,7 @@ create or replace type body ut_run as l_coverage_schema_names ut_varchar2_rows; l_coverage_options ut_coverage_options; l_exclude_objects ut_object_names; - begin + begin self.run_paths := a_run_paths; self.self_type := $$plsql_unit; self.items := a_items; diff --git a/source/core/types/ut_run.tps b/source/core/types/ut_run.tps index 8ff09970e..02d77a93f 100644 --- a/source/core/types/ut_run.tps +++ b/source/core/types/ut_run.tps @@ -1,4 +1,4 @@ -create or replace type ut_run under ut_suite_item ( +create or replace type ut_run force under ut_suite_item ( /* utPLSQL - Version 3 Copyright 2016 - 2017 utPLSQL Project diff --git a/source/dummy.sql b/source/dummy.sql new file mode 100644 index 000000000..36d9f8778 --- /dev/null +++ b/source/dummy.sql @@ -0,0 +1 @@ +whenever sqlerror exit failure rollback diff --git a/source/install.sql b/source/install.sql index cdebc119d..94ebe862f 100644 --- a/source/install.sql +++ b/source/install.sql @@ -114,6 +114,12 @@ alter session set current_schema = &&ut3_owner; prompt Installing PLSQL profiler objects into &&ut3_owner schema @@core/coverage/proftab.sql +prompt Installing PLSQL profiler objects into &&ut3_owner schema +@@core/coverage/proftab.sql + +prompt Installing DBMSPLSQL Tables objects into &&ut3_owner schema +@@core/coverage/dbms_plssqlcode.sql + @@install_component.sql 'core/ut_file_mapper.pks' @@install_component.sql 'core/ut_file_mapper.pkb' @@ -121,10 +127,18 @@ prompt Installing PLSQL profiler objects into &&ut3_owner schema --gathering coverage @@install_component.sql 'core/coverage/ut_coverage_sources_tmp.sql' @@install_component.sql 'core/coverage/ut_coverage_helper.pks' -@@install_component.sql 'core/coverage/ut_coverage_helper.pkb' +@@install_above_12_1.sql 'core/coverage/ut_coverage_helper_block.pks' +@@install_component.sql 'core/coverage/ut_coverage_helper_profiler.pks' @@install_component.sql 'core/coverage/ut_coverage.pks' -@@install_component.sql 'core/coverage/ut_coverage.pkb' +@@install_above_12_1.sql 'core/coverage/ut_coverage_block.pks' +@@install_component.sql 'core/coverage/ut_coverage_profiler.pks' @@install_component.sql 'core/coverage/ut_coverage_reporter_base.tps' +@@install_component.sql 'core/coverage/ut_coverage_helper.pkb' +@@install_above_12_1.sql 'core/coverage/ut_coverage_helper_block.pkb' +@@install_component.sql 'core/coverage/ut_coverage_helper_profiler.pkb' +@@install_component.sql 'core/coverage/ut_coverage.pkb' +@@install_above_12_1.sql 'core/coverage/ut_coverage_block.pkb' +@@install_component.sql 'core/coverage/ut_coverage_profiler.pkb' @@install_component.sql 'core/coverage/ut_coverage_reporter_base.tpb' --core type bodies diff --git a/source/install_above_12_1.sql b/source/install_above_12_1.sql new file mode 100644 index 000000000..69e47ce9a --- /dev/null +++ b/source/install_above_12_1.sql @@ -0,0 +1,27 @@ +def FILE_NAME = '&&1' +column SCRIPT_NAME new_value SCRIPT_NAME noprint + +VAR V_FILE_NAME VARCHAR2(1000); +begin + if dbms_db_version.version = 12 and dbms_db_version.release >= 2 + or dbms_db_version.version > 12 + then + :V_FILE_NAME := '&&FILE_NAME'; + else + :V_FILE_NAME := 'dummy.sql'; + end if; +end; +/ + +set verify off +select :V_FILE_NAME as SCRIPT_NAME from dual; +set termout on + +set heading off +set feedback off +exec dbms_output.put_line('Installing component '||upper(regexp_substr('&&1','\/(\w*)\.',1,1,'i',1))); +@@&&SCRIPT_NAME +exec dbms_output.put_line('&&line_separator'); + + + diff --git a/source/reporters/ut_coverage_cobertura_reporter.tpb b/source/reporters/ut_coverage_cobertura_reporter.tpb index edd783ca6..82d085604 100644 --- a/source/reporters/ut_coverage_cobertura_reporter.tpb +++ b/source/reporters/ut_coverage_cobertura_reporter.tpb @@ -33,6 +33,7 @@ create or replace type body ut_coverage_cobertura_reporter is l_file_part varchar2(32767); l_result clob; l_line_no binary_integer; + l_pct integer; begin dbms_lob.createtemporary(l_result, true); l_line_no := a_unit_coverage.lines.first; @@ -43,10 +44,19 @@ create or replace type body ut_coverage_cobertura_reporter is end loop; else while l_line_no is not null loop - if a_unit_coverage.lines(l_line_no) = 0 then + if a_unit_coverage.lines(l_line_no).executions = 0 then l_file_part := ''||chr(10); else - l_file_part := ''||chr(10); + l_file_part := ''||chr(10); end if; ut_utils.append_to_clob(l_result, l_file_part); l_line_no := a_unit_coverage.lines.next(l_line_no); diff --git a/source/reporters/ut_coverage_html_reporter.tpb b/source/reporters/ut_coverage_html_reporter.tpb index 4ebb0b2fd..d1a9dd473 100644 --- a/source/reporters/ut_coverage_html_reporter.tpb +++ b/source/reporters/ut_coverage_html_reporter.tpb @@ -33,10 +33,9 @@ create or replace type body ut_coverage_html_reporter is l_coverage_data ut_coverage.t_coverage; begin ut_coverage.coverage_stop(); - l_coverage_data := ut_coverage.get_coverage_data(a_run.coverage_options); - self.print_clob( ut_coverage_report_html_helper.get_index( l_coverage_data, self.assets_path, self.project_name ) ); + self.print_clob( ut_coverage_report_html_helper.get_index( a_coverage_data => l_coverage_data,a_assets_path => self.assets_path, a_project_name=> self.project_name )); end; diff --git a/source/reporters/ut_coverage_report_html_helper.pkb b/source/reporters/ut_coverage_report_html_helper.pkb index 8a70b9085..4ea8c6eb1 100644 --- a/source/reporters/ut_coverage_report_html_helper.pkb +++ b/source/reporters/ut_coverage_report_html_helper.pkb @@ -2,13 +2,13 @@ create or replace package body ut_coverage_report_html_helper is /* utPLSQL - Version 3 Copyright 2016 - 2017 utPLSQL Project - + Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,18 +16,18 @@ create or replace package body ut_coverage_report_html_helper is limitations under the License. */ - gc_green_coverage_pct constant integer := 90; - gc_yellow_coverage_pct constant integer := 80; + gc_green_coverage_pct constant integer := 90; + gc_yellow_coverage_pct constant integer := 80; - gc_green_css constant varchar2(10) := 'green'; - gc_yellow_css constant varchar2(10) := 'yellow'; - gc_red_css constant varchar2(10) := 'red'; - - gc_missed constant varchar2(7) := 'missed'; - gc_skipped constant varchar2(7) := 'skipped'; - gc_disabled constant varchar2(7) := 'never'; - gc_covered constant varchar2(7) := 'covered'; + gc_green_css constant varchar2(10) := 'green'; + gc_yellow_css constant varchar2(10) := 'yellow'; + gc_red_css constant varchar2(10) := 'red'; + gc_missed constant varchar2(7) := 'missed'; + gc_skipped constant varchar2(7) := 'skipped'; + gc_disabled constant varchar2(7) := 'never'; + gc_covered constant varchar2(7) := 'covered'; + gc_partcovered constant varchar2(7) := 'partcov'; function get_default_html_assets_path return varchar2 deterministic is c_assets_path constant varchar2(200) := 'https://utplsql.github.io/utPLSQL-coverage-html/assets/'; @@ -42,18 +42,22 @@ create or replace package body ut_coverage_report_html_helper is l_result := gc_green_css; elsif a_covered_pct > gc_yellow_coverage_pct then l_result := gc_yellow_css; - else + else l_result := gc_red_css; end if; return l_result; end; - function line_status(a_executions binary_integer) return varchar2 is + function line_status(a_executions in ut_coverage.t_line_executions) return varchar2 is l_result varchar2(10); begin - if a_executions > 0 then - l_result := gc_covered; - elsif a_executions = 0 then + if a_executions.executions > 0 then + if NVL(a_executions.partcove,0) = 0 then + l_result := gc_covered; + else + l_result := gc_partcovered; + end if; + elsif a_executions.executions = 0 then l_result := gc_missed; else l_result := gc_disabled; @@ -63,130 +67,205 @@ create or replace package body ut_coverage_report_html_helper is function executions_per_line(a_executions number, a_lines integer) return integer is begin - return nvl(a_executions/nullif(a_lines,0),0); + return nvl(a_executions / nullif(a_lines, 0), 0); end; function line_hits_css_class(a_line_hist number) return varchar2 is l_result varchar2(10); begin if a_line_hist > 1 then - l_result := gc_green_css; + l_result := gc_green_css; elsif a_line_hist = 1 then - l_result := gc_yellow_css; - else - l_result := gc_red_css; + l_result := gc_yellow_css; + else + l_result := gc_red_css; end if; return l_result; end; function coverage_pct(a_covered_lines integer, a_uncovered_lines integer) return number is begin - return round(nvl(a_covered_lines/nullif(a_covered_lines+a_uncovered_lines,0),0)*100,2); + return ROUND(nvl(a_covered_lines / nullif(a_covered_lines + a_uncovered_lines, 0), 0) * 100, 2); end; function object_id(a_object_full_name varchar2) return varchar2 is begin - return rawtohex( utl_raw.CAST_TO_RAW(dbms_obfuscation_toolkit.md5(input_string=>a_object_full_name)) ); + return rawtohex(utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string => a_object_full_name))); end; function link_to_source_file(a_object_full_name varchar2) return varchar2 is begin - return ''||a_object_full_name||''; + return '' || a_object_full_name || ''; end; - function get_details_file_content(a_object_id varchar2, a_unit ut_object_name, a_unit_coverage ut_coverage.t_unit_coverage) return clob is + + + +function get_details_file_content(a_object_id varchar2, a_unit ut_object_name, a_unit_coverage ut_coverage.t_unit_coverage) + return clob is l_source_code ut_varchar2_list; l_result clob; - - function build_details_file_content(a_object_id varchar2, a_object_full_name varchar2, a_source_code ut_varchar2_list, a_coverage_unit ut_coverage.t_unit_coverage ) return clob is - l_file_part varchar2(32767); - l_result clob; - l_coverage_pct number(5,2); + + function get_block_file_attributes(a_coverage_unit ut_coverage.t_unit_coverage) return varchar2 is + l_result varchar2(32767); + begin + if (a_coverage_unit.partcovered_lines is not null) AND (a_coverage_unit.partcovered_lines > 0) then + l_result := ' (including '|| a_coverage_unit.partcovered_lines ||' lines partially covered )'; + else + l_result := null; + end if; + return l_result; + end; + + function get_common_file_attributes(a_coverage_unit ut_coverage.t_unit_coverage) return varchar2 is + l_attributes varchar2(32767); + begin + l_attributes := '
' ||(a_coverage_unit.covered_lines + a_coverage_unit.uncovered_lines) + ||' relevant lines. ' || '' || a_coverage_unit.covered_lines + ||' lines covered' + || get_block_file_attributes(a_coverage_unit) + || ' and ' || a_coverage_unit.uncovered_lines ||' lines missed'; + return l_attributes; + end; + + function build_details_file_content(a_object_id varchar2, a_object_full_name varchar2, a_source_code ut_varchar2_list, a_coverage_unit ut_coverage.t_unit_coverage) + return clob is + l_file_part varchar2(32767); + l_result clob; + l_coverage_pct number(5, 2); + l_coverage_block_pct number(5, 2); + l_hits varchar2(30); + l_blocks varchar2(30); begin dbms_lob.createtemporary(l_result, true); - l_coverage_pct := coverage_pct(a_coverage_unit.covered_lines, a_coverage_unit.uncovered_lines); - l_file_part := - '

'||dbms_xmlgen.convert(a_object_full_name)||'

' || - '

'||l_coverage_pct||' % covered

' || - '
'||(a_coverage_unit.covered_lines+a_coverage_unit.uncovered_lines)||' relevant lines. ' || - ''||a_coverage_unit.covered_lines||' lines covered and ' || - ''||a_coverage_unit.uncovered_lines||' lines missed
' || - '
    '; - ut_utils.append_to_clob(l_result, l_file_part); + l_coverage_pct := coverage_pct(a_coverage_unit.covered_lines, a_coverage_unit.uncovered_lines); + + l_file_part := '

    ' || + dbms_xmlgen.convert(a_object_full_name) || '

    ' + || l_coverage_pct || ' % lines covered

    ' + ||get_common_file_attributes(a_coverage_unit) ||'
      '; + ut_utils.append_to_clob(l_result, l_file_part); + for line_no in 1 .. a_source_code.count loop if not a_coverage_unit.lines.exists(line_no) then - l_file_part :=' -
    1. - ' || (dbms_xmlgen.convert(a_source_code(line_no))) || '
    2. '; + l_file_part := ' +
    3. + ' || (dbms_xmlgen.convert(a_source_code(line_no))) || + '
    4. '; else - l_file_part :=' -
    5. '; - if a_coverage_unit.lines(line_no) > 0 then - l_file_part := l_file_part || ' - '||(a_coverage_unit.lines(line_no))||''; + l_hits := to_char(a_coverage_unit.lines(line_no).executions); + if nvl(a_coverage_unit.lines(line_no).covered_blocks,0) < nvl(a_coverage_unit.lines(line_no).no_blocks,0) + and nvl(a_coverage_unit.lines(line_no).partcove,0) = 1 then + l_blocks := to_char(a_coverage_unit.lines(line_no).covered_blocks) || chr(47)|| + to_char(a_coverage_unit.lines(line_no).no_blocks); + else + l_blocks := null; + end if; + + l_file_part := ' +
    6. '; + if a_coverage_unit.lines(line_no).executions > 0 then + + l_file_part := l_file_part || + case when l_blocks is not null + then ' + ' ||dbms_xmlgen.convert(l_blocks) || + '' + else + null + end + || ' + ' || dbms_xmlgen.convert(l_hits) || + ''; end if; l_file_part := l_file_part || ' - ' || (dbms_xmlgen.convert(a_source_code(line_no))) || '
    7. '; + ' || (dbms_xmlgen.convert(a_source_code(line_no))) || + ''; end if; ut_utils.append_to_clob(l_result, l_file_part); end loop; - + l_file_part := '
'; ut_utils.append_to_clob(l_result, l_file_part); return l_result; end; begin l_source_code := ut_coverage_helper.get_tmp_table_object_lines(a_unit.owner, a_unit.name); - dbms_lob.createtemporary(l_result,true); - l_result := build_details_file_content(a_object_id, a_unit.identity, l_source_code, a_unit_coverage); + dbms_lob.createtemporary(l_result, true); + l_result := build_details_file_content(a_object_id + ,a_unit.identity + ,l_source_code + ,a_unit_coverage + ); return l_result; end; + function get_block_list_attributes(a_coverage_unit ut_coverage.t_coverage) return varchar2 is + l_result varchar2(32767); + begin + if (a_coverage_unit.partcovered_lines is not null) AND (a_coverage_unit.partcovered_lines > 0) then + l_result := ' (including '|| a_coverage_unit.partcovered_lines ||' lines partially covered )'; + else + l_result := null; + end if; + return l_result; + end; + function file_list(a_title varchar2, a_coverage ut_coverage.t_coverage) return clob is l_file_part varchar2(32767); l_title varchar2(100) := 'All files'; - l_coverage_pct number(5,2); + l_coverage_pct number(5, 2); + l_coverage_block_pct number(5, 2); l_result clob; l_id varchar2(50) := object_id(a_title); l_unit_coverage ut_coverage.t_unit_coverage; l_unit ut_coverage.t_object_name; begin - dbms_lob.createtemporary(l_result,true); l_coverage_pct := coverage_pct(a_coverage.covered_lines, a_coverage.uncovered_lines); - l_file_part := - '
' || - '

'||l_title||'' || - ' ('||l_coverage_pct||'%' || - ' covered at ' || - ''|| - executions_per_line(a_coverage.executions, a_coverage.uncovered_lines + a_coverage.covered_lines)||' hits/line)

' || - '' || - '
'||a_coverage.objects.count||' files in total. ' || - ''||(a_coverage.uncovered_lines + a_coverage.covered_lines)||' relevant lines. ' || - ''||a_coverage.covered_lines||' lines covered and ' || - ''||a_coverage.uncovered_lines||' lines missed
' || - '' || - '' || - '' || - ''; + + dbms_lob.createtemporary(l_result, true); + + l_file_part := '
' || '

' || l_title || + '' || ' (' || + l_coverage_pct || '%' || ' lines covered'|| + ' at ' || + '' || + executions_per_line(a_coverage.executions, a_coverage.uncovered_lines + a_coverage.covered_lines) + || ' hits/line)

' || '' || '
' || + a_coverage.objects.count || ' files in total.
' || '' || + (a_coverage.uncovered_lines + a_coverage.covered_lines) + || ' relevant lines. ' || '' || a_coverage.covered_lines || + ' lines covered'|| get_block_list_attributes(a_coverage) + ||' and ' || a_coverage.uncovered_lines || ' lines missed.'|| + '
File% coveredLinesRelevant LinesLines coveredLines missedAvg. Hits / Line
' || '' || + '' || + ''; ut_utils.append_to_clob(l_result, l_file_part); l_unit := a_coverage.objects.first; loop exit when l_unit is null; l_unit_coverage := a_coverage.objects(l_unit); l_coverage_pct := coverage_pct(l_unit_coverage.covered_lines, l_unit_coverage.uncovered_lines); - l_file_part := - chr(10)|| - '' || - '' || - '' || - '' || - '' || - '' || - '' || - '' || - ''; + + l_file_part := chr(10) || '' || '' || '' || '' || '' || '' || + ''; ut_utils.append_to_clob(l_result, l_file_part); l_unit := a_coverage.objects.next(l_unit); end loop; @@ -198,59 +277,70 @@ create or replace package body ut_coverage_report_html_helper is /* * public definitions */ - function get_index(a_coverage_data ut_coverage.t_coverage, a_assets_path varchar2, a_project_name varchar2 := null, a_command_line varchar2 := null) return clob is - - l_file_part varchar2(32767); - l_result clob; - l_title varchar2(250); - l_coverage_pct number(5,2); - l_time_str varchar2(50); - l_using varchar2(1000); - l_unit ut_coverage.t_full_name; + function get_index(a_coverage_data ut_coverage.t_coverage, a_assets_path varchar2, a_project_name varchar2 := null, a_command_line varchar2 := null) + return clob is + + l_file_part varchar2(32767); + l_result clob; + l_title varchar2(250); + l_coverage_pct number(5, 2); + l_time_str varchar2(50); + l_using varchar2(1000); + l_unit ut_coverage.t_full_name; begin - l_coverage_pct := coverage_pct(a_coverage_data.covered_lines, a_coverage_data.uncovered_lines); - l_time_str := ut_utils.to_string(sysdate); - l_using := case when a_command_line is not null then '
using '||dbms_xmlgen.convert(a_command_line) end; - dbms_lob.createtemporary(l_result,true); + l_coverage_pct := coverage_pct(a_coverage_data.covered_lines, a_coverage_data.uncovered_lines); - l_title := case when a_project_name is null then 'Code coverage' else dbms_xmlgen.convert(a_project_name) ||' code coverage' end; + l_time_str := ut_utils.to_string(sysdate); + l_using := case + when a_command_line is not null then + '
using ' || dbms_xmlgen.convert(a_command_line) + end; + dbms_lob.createtemporary(l_result, true); + + l_title := case + when a_project_name is null then + 'Code coverage' + else + dbms_xmlgen.convert(a_project_name) || ' code coverage' + end; --TODO - build main file containing total run data and per schema data - l_file_part := - '' || - ''||l_title||'' || - '' || - '' || - '' || - '' || - '' || - '' || - '
loading
' || - ''; + ut_utils.append_to_clob(l_result, l_file_part); return l_result; end; diff --git a/source/reporters/ut_coverage_report_html_helper.pks b/source/reporters/ut_coverage_report_html_helper.pks index e25e308a0..e718f9fed 100644 --- a/source/reporters/ut_coverage_report_html_helper.pks +++ b/source/reporters/ut_coverage_report_html_helper.pks @@ -17,6 +17,20 @@ create or replace package ut_coverage_report_html_helper authid current_user is */ function get_default_html_assets_path return varchar2 deterministic; + function coverage_pct(a_covered_lines integer, a_uncovered_lines integer) return number; + + function coverage_css_class(a_covered_pct number) return varchar2; + + function line_status(a_executions in ut_coverage.t_line_executions) return varchar2; + + function link_to_source_file(a_object_full_name varchar2) return varchar2; + + function object_id(a_object_full_name varchar2) return varchar2; + + function executions_per_line(a_executions number, a_lines integer) return integer; + + function line_hits_css_class(a_line_hist number) return varchar2; + function get_index(a_coverage_data ut_coverage.t_coverage, a_assets_path varchar2, a_project_name varchar2 := null, a_command_line varchar2 := null) return clob; end; diff --git a/source/reporters/ut_coverage_sonar_reporter.tpb b/source/reporters/ut_coverage_sonar_reporter.tpb index 48b04ffdd..a82097061 100644 --- a/source/reporters/ut_coverage_sonar_reporter.tpb +++ b/source/reporters/ut_coverage_sonar_reporter.tpb @@ -42,10 +42,15 @@ create or replace type body ut_coverage_sonar_reporter is end loop; else while l_line_no is not null loop - if a_unit_coverage.lines(l_line_no) = 0 then + if a_unit_coverage.lines(l_line_no).executions = 0 then l_file_part := ''||chr(10); else - l_file_part := ''||chr(10); + l_file_part := ''||chr(10); end if; ut_utils.append_to_clob(l_result, l_file_part); l_line_no := a_unit_coverage.lines.next(l_line_no); diff --git a/source/reporters/ut_coveralls_reporter.tpb b/source/reporters/ut_coveralls_reporter.tpb index c13084415..f19d1f3ce 100644 --- a/source/reporters/ut_coveralls_reporter.tpb +++ b/source/reporters/ut_coveralls_reporter.tpb @@ -48,7 +48,7 @@ create or replace type body ut_coveralls_reporter is else for line_no in 1 .. l_last_line_no loop if a_unit_coverage.lines.exists(line_no) then - l_file_part := to_char(a_unit_coverage.lines(line_no)); + l_file_part := to_char(a_unit_coverage.lines(line_no).executions); else l_file_part := c_null; end if; diff --git a/test/core/reporters/test_coverage.pkb b/test/core/reporters/test_coverage.pkb index 18df7c231..7a759ec43 100644 --- a/test/core/reporters/test_coverage.pkb +++ b/test/core/reporters/test_coverage.pkb @@ -106,7 +106,7 @@ create or replace package body test_coverage is create_dummy_coverage_package(); create_dummy_coverage_test(); g_run_id := get_mock_run_id(); - ut3.ut_coverage_helper.mock_coverage_id(g_run_id); + ut3.ut_coverage_helper.mock_coverage_id(g_run_id,ut3.ut_coverage.gc_proftab_coverage); mock_coverage_data(g_run_id); commit; end; diff --git a/test/core/reporters/test_coverage.pks b/test/core/reporters/test_coverage.pks index 735bd35c0..d507fa0ee 100644 --- a/test/core/reporters/test_coverage.pks +++ b/test/core/reporters/test_coverage.pks @@ -10,18 +10,18 @@ create or replace package test_coverage is procedure cleanup_dummy_coverage; - --%test(Coverage is gathered for specified object) + --%test(Coverage is gathered for specified object - default coverage type) procedure coverage_for_object; - --%test(Coverage is gathered for specified object in the schema defined by run path) + --%test(Coverage is gathered for specified schema - default coverage type) procedure coverage_for_object_no_owner; --%test(Coverage is gathered for specified schema) procedure coverage_for_schema; - --%test(Coverage is gathered for specified file) + --%test(Coverage is gathered for specified file - default coverage type) procedure coverage_for_file; - + --%test(Coverage data is not cached between runs - issue #562 ) --%aftertest(setup_dummy_coverage) procedure coverage_tmp_data_refresh; diff --git a/test/core/reporters/test_coverage/test_html_extended_reporter.pkb b/test/core/reporters/test_coverage/test_html_extended_reporter.pkb new file mode 100644 index 000000000..cd71a6c8c --- /dev/null +++ b/test/core/reporters/test_coverage/test_html_extended_reporter.pkb @@ -0,0 +1,27 @@ +create or replace package body test_html_extended_reporter is + + procedure report_on_file is + l_results ut3.ut_varchar2_list; + l_expected varchar2(32767); + l_actual clob; + begin + --Arrange + l_expected := '%

UT3.DUMMY_COVERAGE

%4 relevant lines. 3 lines covered (including 1 lines partially covered ) and 1 lines missed%'; + + select * + bulk collect into l_results + from table( + ut3.ut.run( + a_path => 'ut3.test_dummy_coverage', + a_reporter=> ut3.ut_coverage_html_reporter(), + a_source_files => ut3.ut_varchar2_list( 'test/ut3.dummy_coverage.pkb' ), + a_test_files => ut3.ut_varchar2_list( ) + ) + ); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like(l_expected); + end; + +end test_html_extended_reporter; +/ diff --git a/test/core/reporters/test_coverage/test_html_extended_reporter.pks b/test/core/reporters/test_coverage/test_html_extended_reporter.pks new file mode 100644 index 000000000..cefa71d66 --- /dev/null +++ b/test/core/reporters/test_coverage/test_html_extended_reporter.pks @@ -0,0 +1,10 @@ +create or replace package test_html_extended_reporter is + + --%suite(ut_html_extended_reporter) + --%suitepath(utplsql.core.reporters.test_extended_coverage) + + --%test(reports on a project file mapped to database object in extended profiler coverage) + procedure report_on_file; + +end test_html_extended_reporter; +/ diff --git a/test/core/reporters/test_coverage/test_html_proftab_reporter.pkb b/test/core/reporters/test_coverage/test_html_proftab_reporter.pkb new file mode 100644 index 000000000..7167dc6f6 --- /dev/null +++ b/test/core/reporters/test_coverage/test_html_proftab_reporter.pkb @@ -0,0 +1,29 @@ +create or replace package body test_html_proftab_reporter is + + procedure report_on_file is + l_results ut3.ut_varchar2_list; + l_expected varchar2(32767); + l_actual clob; + begin + --Arrange + l_expected := '%

UT3.DUMMY_COVERAGE

%3 relevant lines. 2 lines covered and 1 lines missed%'; + + --l_expected := '%

UT3.DUMMY_COVERAGE

66% lines covered

3 relevant lines. 2 lines covered ) and 1 lines missed
%'; + --Act + select * + bulk collect into l_results + from table( + ut3.ut.run( + a_path => 'ut3.test_dummy_coverage', + a_reporter=> ut3.ut_coverage_html_reporter(), + a_source_files => ut3.ut_varchar2_list( 'test/ut3.dummy_coverage.pkb' ), + a_test_files => ut3.ut_varchar2_list( ) + ) + ); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like(l_expected); + end; + +end test_html_proftab_reporter; +/ diff --git a/test/core/reporters/test_coverage/test_html_proftab_reporter.pks b/test/core/reporters/test_coverage/test_html_proftab_reporter.pks new file mode 100644 index 000000000..d091510c6 --- /dev/null +++ b/test/core/reporters/test_coverage/test_html_proftab_reporter.pks @@ -0,0 +1,10 @@ +create or replace package test_html_proftab_reporter is + + --%suite(ut_html_proftab_reporter) + --%suitepath(utplsql.core.reporters.test_coverage) + + --%test(reports on a project file mapped to database object in profiler coverage) + procedure report_on_file; + +end test_html_proftab_reporter; +/ diff --git a/test/core/reporters/test_extended_coverage.pkb b/test/core/reporters/test_extended_coverage.pkb new file mode 100644 index 000000000..7a7668b6c --- /dev/null +++ b/test/core/reporters/test_extended_coverage.pkb @@ -0,0 +1,188 @@ +create or replace package body test_extended_coverage is + + g_run_id ut3.ut_coverage_helper.g_coverage_arr; + + function get_mock_block_run_id return integer is + v_result integer; + begin + select nvl(min(run_id),0) - 1 into v_result + from ut3.dbmspcc_runs; + return v_result; + end; + + function get_mock_proftab_run_id return integer is + v_result integer; + begin + select nvl(min(runid),0) - 1 into v_result + from ut3.plsql_profiler_runs; + return v_result; + end; + + procedure create_dummy_coverage_package is + pragma autonomous_transaction; + begin + execute immediate q'[create or replace package UT3.DUMMY_COVERAGE is + procedure do_stuff(i_input in number); + end;]'; + execute immediate q'[create or replace package body UT3.DUMMY_COVERAGE is + procedure do_stuff(i_input in number) is + begin + if i_input = 2 then + dbms_output.put_line('should not get here'); + else + dbms_output.put_line('should get here'); + end if; + end; + end;]'; + end; + + procedure create_dummy_coverage_test is + pragma autonomous_transaction; + begin + execute immediate q'[create or replace package UT3.TEST_DUMMY_COVERAGE is + --%suite(dummy coverage test) + --%suitepath(coverage_testing) + + --%test + procedure test_do_stuff; + end;]'; + execute immediate q'[create or replace package body UT3.TEST_DUMMY_COVERAGE is + procedure test_do_stuff is + begin + dummy_coverage.do_stuff(1); + ut.expect(1).to_equal(1); + end; + end;]'; + end; + + procedure mock_block_coverage_data(a_run_id integer) is + c_unit_id constant integer := 1; + begin + insert into ut3.dbmspcc_runs ( run_id, run_owner, run_timestamp, run_comment) + values(a_run_id, user, sysdate, 'unit testing utPLSQL'); + + insert into ut3.dbmspcc_units ( run_id, object_id, type, owner, name,last_ddl_time) + values(a_run_id, c_unit_id, 'PACKAGE BODY', 'UT3', 'DUMMY_COVERAGE',sysdate); + + insert into ut3.dbmspcc_blocks ( run_id, object_id, line,block,col,covered,not_feasible) + select a_run_id, c_unit_id,4,1,1,1,0 from dual union all + select a_run_id, c_unit_id,4,2,2,0,0 from dual union all + select a_run_id, c_unit_id,5,3,0,1,0 from dual union all + select a_run_id, c_unit_id,7,4,1,1,0 from dual; + end; + + procedure mock_profiler_coverage_data(a_run_id integer) is + c_unit_id constant integer := 1; + begin + insert into ut3.plsql_profiler_runs ( runid, run_owner, run_date, run_comment) + values(a_run_id, user, sysdate, 'unit testing utPLSQL'); + + insert into ut3.plsql_profiler_units ( runid, unit_number, unit_type, unit_owner, unit_name) + values(a_run_id, c_unit_id, 'PACKAGE BODY', 'UT3', 'DUMMY_COVERAGE'); + + insert into ut3.plsql_profiler_data ( runid, unit_number, line#, total_occur, total_time) + select a_run_id, c_unit_id, 4, 1, 1 from dual union all + select a_run_id, c_unit_id, 5, 0, 0 from dual union all + select a_run_id, c_unit_id, 6, 1, 0 from dual union all + select a_run_id, c_unit_id, 7, 1, 1 from dual; + end; + + procedure setup_dummy_coverage is + pragma autonomous_transaction; + begin + create_dummy_coverage_package(); + create_dummy_coverage_test(); + g_run_id(ut3.ut_coverage.gc_block_coverage) := get_mock_block_run_id(); + g_run_id(ut3.ut_coverage.gc_proftab_coverage) := get_mock_proftab_run_id(); + ut3.ut_coverage_helper.mock_coverage_id(g_run_id); + mock_block_coverage_data(g_run_id(ut3.ut_coverage.gc_block_coverage)); + mock_profiler_coverage_data(g_run_id(ut3.ut_coverage.gc_proftab_coverage)); + commit; + end; + + procedure cleanup_dummy_coverage is + pragma autonomous_transaction; + begin + begin execute immediate q'[drop package ut3.test_dummy_coverage]'; exception when others then null; end; + begin execute immediate q'[drop package ut3.dummy_coverage]'; exception when others then null; end; + delete from ut3.dbmspcc_blocks where run_id = g_run_id(ut3.ut_coverage.gc_block_coverage); + delete from ut3.dbmspcc_units where run_id = g_run_id(ut3.ut_coverage.gc_block_coverage); + delete from ut3.dbmspcc_runs where run_id = g_run_id(ut3.ut_coverage.gc_block_coverage); + delete from ut3.plsql_profiler_data where runid = g_run_id(ut3.ut_coverage.gc_proftab_coverage); + delete from ut3.plsql_profiler_units where runid = g_run_id(ut3.ut_coverage.gc_proftab_coverage); + delete from ut3.plsql_profiler_runs where runid = g_run_id(ut3.ut_coverage.gc_proftab_coverage); + commit; + end; + + procedure coverage_for_object is + l_expected clob; + l_actual clob; + l_results ut3.ut_varchar2_list; + begin + --Arrange + l_expected := '%%%'; + --Act + select * + bulk collect into l_results + from table( + ut3.ut.run( + a_path => 'ut3.test_dummy_coverage', + a_reporter=> ut3.ut_coverage_sonar_reporter( ), + a_include_objects => ut3.ut_varchar2_list( 'ut3.dummy_coverage' ) + ) + ); + --Assert + l_actual := ut3.ut_utils.table_to_clob(l_results); + ut.expect(l_actual).to_be_like(l_expected); + end; + + procedure coverage_for_schema is + l_expected clob; + l_actual clob; + l_results ut3.ut_varchar2_list; + begin + --Arrange + l_expected := '%%%'; + --Act + select * + bulk collect into l_results + from table( + ut3.ut.run( + a_path => 'ut3.test_dummy_coverage', + a_reporter=> ut3.ut_coverage_sonar_reporter( ), + a_coverage_schemes => ut3.ut_varchar2_list( 'ut3' ) + ) + ); + --Assert + l_actual := ut3.ut_utils.table_to_clob(l_results); + ut.expect(l_actual).to_be_like(l_expected); + ut.expect(l_actual).to_be_like('%%%'); + end; + + procedure coverage_for_file is + l_expected clob; + l_actual clob; + l_results ut3.ut_varchar2_list; + l_file_path varchar2(100); + begin + --Arrange + l_file_path := lower('test/ut3.dummy_coverage.pkb'); + l_expected := '%%%'; + --Act + select * + bulk collect into l_results + from table( + ut3.ut.run( + a_path => 'ut3.test_dummy_coverage', + a_reporter=> ut3.ut_coverage_sonar_reporter( ), + a_source_files => ut3.ut_varchar2_list( l_file_path ), + a_test_files => ut3.ut_varchar2_list( ) + ) + ); + --Assert + l_actual := ut3.ut_utils.table_to_clob(l_results); + ut.expect(l_actual).to_be_like(l_expected); + end; + +end; +/ diff --git a/test/core/reporters/test_extended_coverage.pks b/test/core/reporters/test_extended_coverage.pks new file mode 100644 index 000000000..6d85a4a30 --- /dev/null +++ b/test/core/reporters/test_extended_coverage.pks @@ -0,0 +1,22 @@ +create or replace package test_extended_coverage is + + --%suite + --%suitepath(utplsql.core.reporters) + + --%beforeall + procedure setup_dummy_coverage; + + --%afterall + procedure cleanup_dummy_coverage; + + --%test(Coverage is gathered for specified object - extended coverage type) + procedure coverage_for_object; + + --%test(Coverage is gathered for specified schema - extended coverage type) + procedure coverage_for_schema; + + --%test(Coverage is gathered for specified file - extended coverage type) + procedure coverage_for_file; + +end; +/ diff --git a/test/core/reporters/test_tfs_junit_reporter.pkb b/test/core/reporters/test_tfs_junit_reporter.pkb index f7ea923ff..267ba6ab0 100644 --- a/test/core/reporters/test_tfs_junit_reporter.pkb +++ b/test/core/reporters/test_tfs_junit_reporter.pkb @@ -141,7 +141,7 @@ create or replace package body test_tfs_junit_reporter as from table(ut3.ut.run('check_junit_reporting', ut3.ut_tfs_junit_reporter())); l_actual := ut3.ut_utils.table_to_clob(l_results); --Assert - ut.expect(l_actual).to_match('time="[0-9]*\.[0-9]{6}"'); + ut.expect(l_actual).to_match('time="[0-9]*\.[0-9]{3,6}"'); --Cleanup execute immediate 'alter session set NLS_NUMERIC_CHARACTERS='''||l_nls_numeric_characters||''''; end; diff --git a/test/core/reporters/test_xunit_reporter.pkb b/test/core/reporters/test_xunit_reporter.pkb new file mode 100644 index 000000000..f75c53ce4 --- /dev/null +++ b/test/core/reporters/test_xunit_reporter.pkb @@ -0,0 +1,133 @@ +create or replace package body test_xunit_reporter as + + procedure crate_a_test_package is + pragma autonomous_transaction; + begin + execute immediate q'[create or replace package check_xunit_reporting is + --%suite(A suite with ) + + --%test(A test with ) + procedure test_do_stuff; + + end;]'; + execute immediate q'[create or replace package body check_xunit_reporting is + procedure test_do_stuff is + begin + ut3.ut.expect(1).to_equal(1); + ut3.ut.expect(1).to_equal(2); + end; + + end;]'; + + execute immediate q'[create or replace package check_xunit_rep_suitepath is + --%suitepath(core) + --%suite(check_xunit_rep_suitepath) + --%displayname(Check XUNIT Get path for suitepath) + + --%test(check_xunit_rep_suitepath) + --%displayname(Check XUNIT Get path for suitepath) + procedure check_xunit_rep_suitepath; + end;]'; + execute immediate q'[create or replace package body check_xunit_rep_suitepath is + procedure check_xunit_rep_suitepath is + begin + ut3.ut.expect(1).to_equal(1); + end; + end;]'; + end; + + procedure escapes_special_chars is + l_results ut3.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3.ut.run('check_xunit_reporting',ut3.ut_xunit_reporter())); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).not_to_be_like('%%'); + ut.expect(l_actual).to_be_like('%<tag>%'); + end; + + procedure reports_only_failed_or_errored is + l_results ut3.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3.ut.run('check_xunit_reporting',ut3.ut_xunit_reporter())); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).not_to_be_like('%Actual: 1 (number) was expected to equal: 1 (number)%'); + ut.expect(l_actual).to_be_like('%Actual: 1 (number) was expected to equal: 2 (number)%'); + end; + + procedure reports_failed_line is + l_results ut3.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3.ut.run('check_xunit_reporting',ut3.ut_xunit_reporter())); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like('%at "%.CHECK_XUNIT_REPORTING%", line %'); + end; + + procedure check_classname_suite is + l_results ut3.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3.ut.run('check_xunit_reporting',ut3.ut_xunit_reporter())); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like('%testcase classname="check_xunit_reporting"%'); + end; + + procedure check_nls_number_formatting is + l_results ut3.ut_varchar2_list; + l_actual clob; + l_nls_numeric_characters varchar2(30); + begin + --Arrange + select nsp.value into l_nls_numeric_characters + from nls_session_parameters nsp + where parameter = 'NLS_NUMERIC_CHARACTERS'; + execute immediate q'[alter session set NLS_NUMERIC_CHARACTERS=', ']'; + --Act + select * + bulk collect into l_results + from table(ut3.ut.run('check_xunit_reporting', ut3.ut_xunit_reporter())); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_match('time="[0-9]*\.[0-9]{3,6}"'); + --Cleanup + execute immediate 'alter session set NLS_NUMERIC_CHARACTERS='''||l_nls_numeric_characters||''''; + end; + + procedure check_classname_suitepath is + l_results ut3.ut_varchar2_list; + l_actual clob; + begin + --Act + select * + bulk collect into l_results + from table(ut3.ut.run('check_xunit_rep_suitepath',ut3.ut_xunit_reporter())); + l_actual := ut3.ut_utils.table_to_clob(l_results); + --Assert + ut.expect(l_actual).to_be_like('%testcase classname="core.check_xunit_rep_suitepath"%'); + end; + procedure remove_test_package is + pragma autonomous_transaction; + begin + execute immediate 'drop package check_xunit_reporting'; + execute immediate 'drop package check_xunit_rep_suitepath'; + end; +end; +/ diff --git a/test/install_above_12_1.sql b/test/install_above_12_1.sql new file mode 100644 index 000000000..fe20cf816 --- /dev/null +++ b/test/install_above_12_1.sql @@ -0,0 +1,28 @@ +set termout off +set echo off +spool dummy.sql +prompt whenever sqlerror exit failure rollback +spool off + + +def FILE_NAME = '&&1' +column SCRIPT_NAME new_value SCRIPT_NAME noprint + +VAR V_FILE_NAME VARCHAR2(1000); +begin + if dbms_db_version.version = 12 and dbms_db_version.release >= 2 + or dbms_db_version.version > 12 + then + :V_FILE_NAME := '&&FILE_NAME'; + else + :V_FILE_NAME := 'dummy.sql'; + end if; +end; +/ +set verify off +select :V_FILE_NAME as SCRIPT_NAME from dual; +set termout on + + +@@&&SCRIPT_NAME + diff --git a/test/install_and_run_tests.sh b/test/install_and_run_tests.sh index b96602f15..444346bb2 100644 --- a/test/install_and_run_tests.sh +++ b/test/install_and_run_tests.sh @@ -5,6 +5,7 @@ set -ev #goto git root directory git rev-parse && cd "$(git rev-parse --show-cdup)" + cd test "$SQLCLI" ${UT3_TESTER}/${UT3_TESTER_PASSWORD}@//${CONNECTION_STR} @install_tests.sql diff --git a/test/install_below_12_2.sql b/test/install_below_12_2.sql new file mode 100644 index 000000000..669ae75fc --- /dev/null +++ b/test/install_below_12_2.sql @@ -0,0 +1,28 @@ +set termout off +set echo off +spool dummy.sql +prompt whenever sqlerror exit failure rollback +spool off + + +def FILE_NAME = '&&1' +column SCRIPT_NAME new_value SCRIPT_NAME noprint + +VAR V_FILE_NAME VARCHAR2(1000); +begin + if dbms_db_version.version = 12 and dbms_db_version.release < 2 + or dbms_db_version.version < 12 + then + :V_FILE_NAME := '&&FILE_NAME'; + else + :V_FILE_NAME := 'dummy.sql'; + end if; +end; +/ +set verify off +select :V_FILE_NAME as SCRIPT_NAME from dual; +set termout on + + +@@&&SCRIPT_NAME + diff --git a/test/install_tests.sql b/test/install_tests.sql index 71b6fce24..5c97dc0b5 100644 --- a/test/install_tests.sql +++ b/test/install_tests.sql @@ -28,10 +28,17 @@ whenever oserror exit failure rollback @@core/test_suite_manager.pks @@core/reporters.pks @@core/reporters/test_coverage.pks +set define on +@@install_above_12_1.sql 'core/reporters/test_extended_coverage.pks' +@@install_above_12_1.sql 'core/reporters/test_coverage/test_html_extended_reporter.pks' +set define off @@core/reporters/test_coverage/test_coverage_sonar_reporter.pks @@core/reporters/test_coverage/test_coveralls_reporter.pks @@core/reporters/test_coverage/test_cov_cobertura_reporter.pks @@core/reporters/test_junit_reporter.pks +set define on +@@install_below_12_2.sql 'core/reporters/test_coverage/test_html_proftab_reporter.pks' +set define off @@core/reporters/test_tfs_junit_reporter.pks @@core/expectations.pks @@core/expectations/scalar_data/binary/test_be_greater_or_equal.pks @@ -63,10 +70,17 @@ whenever oserror exit failure rollback @@core/test_suite_manager.pkb @@core/reporters.pkb @@core/reporters/test_coverage.pkb +set define on +@@install_above_12_1.sql 'core/reporters/test_extended_coverage.pkb' +@@install_above_12_1.sql 'core/reporters/test_coverage/test_html_extended_reporter.pkb' +set define off @@core/reporters/test_coverage/test_coverage_sonar_reporter.pkb @@core/reporters/test_coverage/test_coveralls_reporter.pkb @@core/reporters/test_coverage/test_cov_cobertura_reporter.pkb @@core/reporters/test_junit_reporter.pkb +set define on +@@install_below_12_2.sql 'core/reporters/test_coverage/test_html_proftab_reporter.pkb' +set define off @@core/reporters/test_tfs_junit_reporter.pkb @@core/expectations.pkb @@core/expectations/scalar_data/binary/test_be_greater_or_equal.pkb
File% coveredLinesRelevant LinesLines coveredLines missed' + ||'Avg. Hits / Line
'||link_to_source_file(dbms_xmlgen.convert(l_unit))||''||l_coverage_pct||' %'||l_unit_coverage.total_lines||''||(l_unit_coverage.covered_lines+l_unit_coverage.uncovered_lines)||''||l_unit_coverage.covered_lines||''||l_unit_coverage.uncovered_lines||''||executions_per_line(l_unit_coverage.executions, l_unit_coverage.uncovered_lines + l_unit_coverage.covered_lines)||'
' || link_to_source_file(dbms_xmlgen.convert(l_unit)) || + '' || l_coverage_pct || + ' %' || l_unit_coverage.total_lines || '' || + (l_unit_coverage.covered_lines + l_unit_coverage.uncovered_lines) || '' || + l_unit_coverage.covered_lines || '' || l_unit_coverage.uncovered_lines || '' || to_char(executions_per_line(l_unit_coverage.executions + ,l_unit_coverage.uncovered_lines + l_unit_coverage.covered_lines)) + || '