10000 Standalone coverage by jgebal · Pull Request #1079 · utPLSQL/utPLSQL · GitHub
[go: up one dir, main page]

Skip to content
8000

Standalone coverage #1079

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 130 additions & 13 deletions docs/userguide/coverage.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
![version](https://img.shields.io/badge/version-v3.1.11.3392--develop-blue.svg)

# Coverage
utPLSQL comes with a built-in coverage reporting engine. The code coverage reporting is based on the DBMS_PROFILER package provided with Oracle database.
utPLSQL comes with a built-in coverage reporting engine. The code coverage reporting uses DBMS_PROFILER package provided with Oracle database.
Code coverage is gathered for the following source types:
* package bodies
* type bodies
Expand All @@ -11,9 +11,9 @@ Code coverage is gathered for the following source types:

**Note**

> The package and type specifications are explicitly excluded from code coverage analysis. This limitation is introduced to avoid false-negatives. Typically package specifications contain no executable code. The only exception is initialization of global constants and variables in package specification. Since most package specifications are not executable at all, there is no information available on the number of lines covered and those would be reported as 0% covered, which is not desirable.
> The package and type specifications are excluded from code coverage analysis. This limitation is introduced to avoid false-negatives. Typically package specifications contain no executable code. The only exception is initialization of global constants and variables in package specification. Since most package specifications are not executable at all, there is no information available on the number of lines covered and those would be reported as 0% covered, which is not desirable.

To obtain information about code coverage of your unit tests, all you need to do is run your unit tests with one of built-in code coverage reporters.
To obtain information about code coverage for unit tests, run utPLSQL with one of built-in code coverage reporters.
The following code coverage reporters are supplied with utPLSQL:
* `ut_coverage_html_reporter` - generates a HTML coverage report providing summary and detailed information on code coverage. The HTML reporter is based on the open-source [simplecov-html](https://github.com/colszowka/simplecov-html) reporter for Ruby. It includes source code of the code that was covered (if possible)
* `ut_coveralls_reporter` - generates a [Coveralls compatible JSON](https://coveralls.zendesk.com/hc/en-us/articles/201774865-API-Introduction) coverage report providing detailed information on code coverage with line numbers. This coverage report is designed to be consumed by cloud services like [Coveralls](https://coveralls.io)
Expand All @@ -33,8 +33,8 @@ If you have `execute` privilege only on the unit tests, but do not have `execute
If the code that is being tested is complied as NATIVE, the code coverage will not be reported as well.

## Running unit tests with coverage
Using the code coverage functionality is as easy as using any other [reporter](reporters.md) for the utPLSQL project. You just run your tests from your preferred SQL tool and save the reporter results to a file.
All you need to do is pass the constructor of the reporter to your `ut.run`
Using the code coverage functionality is as easy as using any other [reporter](reporters.md) for the utPLSQL test-run. You just run your tests from your preferred SQL tool and save the reporter results to a file.
All you need to do is pass the constructor of the reporter to the `ut.run` procedure call.

Example:
```sql
Expand All @@ -43,10 +43,10 @@ begin
end;
/
```
Executes all unit tests in the current schema, gathers information about code coverage and outputs the HTML text into DBMS_OUTPUT.
The above command executes all unit tests in the **current schema**, gathers information about code coverage and outputs the HTML report as text into DBMS_OUTPUT.
The `ut_coverage_html_reporter` will produce an interactive HTML report. You can see a sample of code coverage for the utPLSQL project [here](https://utplsql.github.io/utPLSQL-coverage-html/)

The report provides summary information with a list of source code that was expected to be covered.
The report provides summary information with a list of source code that should be covered.

![Coverage Summary page](../images/coverage_html_summary.png)

Expand All @@ -57,8 +57,8 @@ The report allow you to navigate to each source file and inspect line by line co

#### 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, displaying number of block and how many blocks been covered in that line.The feature will be automatically enabled in the Oracle database version 12.2 and higher, for older versions current profiler will be used.
For every line recorded by the profiler if we have a partially covered same line in block coverage we will display that information
presenting line as partially covered, displaying number of block and how many blocks have been covered in that line.The feature will be automatically enabled in the Oracle database version 12.2 and higher, for older versions current profiler will be used.

utPLSQL installation automatically creates tables needed by `dbms_plsql_code_coverage` on databases in versions above 12c Release 1.
Due to security model of `dbms_plsql_code_coverage` package, utPLSQL grants access to those tables and creates synonyms for those tables.
Expand Down Expand Up @@ -92,7 +92,8 @@ The default behavior of coverage reporting can be altered using invocation param

### Schema based Coverage

To simply gather coverage for all objects in your current schema execute tests with coverage reporting.
To gather coverage for all objects in the **current schema** execute tests with coverage report as argument.
This is the default reporting option and therefore additional coverage options don't need to be provided.

```sql
exec ut.run(ut_coverage_html_reporter());
Expand All @@ -109,7 +110,7 @@ exec ut.run(ut_coverage_html_reporter());
#### Setting coverage schema(s)

By default, coverage is gathered on the schema(s) derived from suite paths provided to execute tests.
This is correct as long as your test packages and tested code share the same schema.
This is a valid approach as long as your test packages and tested code share the same schema.

So when you run:
```sql
Expand Down Expand Up @@ -531,8 +532,8 @@ Unit test code is mapped to files in `test_results.xml`

In order to allow deterministic and accurate mapping of database source-code into project files, the project directory and file structure needs to meet certain criteria.
- Source code is kept separate from test code (separate directories)
- Each database (source-code) object is stored in individual file. Package/type specification is kept separate from it's body.
- File name (file path) contains name of database object
- Each database (source-code) object is stored in an individual file. Package/type specification is kept separate from its body.
- File name (file path) contains the name of database object
- Each file-path clearly identifies object type (by file extension)
- Each file contains representation of database object "as is". No extra commands (like `set echo off` `ALTER SESSION SET PLSQL_CCFLAGS = 'debug:TRUE';`) or blank lines are present before `CREATE TYPE`,`CREATE TYPE` etc.
- When project is spanning across multiple database schemes, each file-path clearly and uniformly identifies object owner
Expand Down Expand Up @@ -659,3 +660,119 @@ begin
end;
```

## Reporting coverage outside of utPLSQL

utPSLQL allows fo standalone reporting code coverage across multiple database sessions. This functionality enables coverage reporting for external testing tools.

Following API calls enable the standalone coverage reporting.

- `ut_runner.coverage_start( coverage_run_id );` - initiates code coverage within a session
- `ut_runner.coverage_stop();` - stops gathering of code coverage within a session
- `.get_report( ... )` - coverage reporters function producing coverage report as pipelined data-set (to be used in SQL query)
- `.get_report_cursor( ... )` - coverage reporters function producing coverage report as ref-cursor

Example:
```sql
--SESSION 1
-- gather coverage on code using specific coverage_run_id value
declare
l_coverage_run_id raw(32);
begin
l_coverage_run_id := 'A6AA5B7361251CE6E053020011ACA055';
-- l_coverage_run_id := sys_guid;
ut_runner.coverage_start(l_coverage_run_id);

--The code to gather coverage on goes here

ut_runner.coverage_stop();
end;
/
```

```sql
--SESSION 2
-- alternative approach
-- gather coverage on code using specific coverage_run_id value
exec ut_runner.coverage_start('A6AA5B7361251CE6E053020011ACA055');

--The code to gather coverage on goes here

exec ut_runner.coverage_stop();
```


```sql
--SESSION 1 or SESSION2 2 or SESSION 3
-- run after calls in SESSION 1 & 2 are finshed
-- retrieve coverage report in HTML format coverage_run_id value
select *
from table(
ut_coverage_html_reporter().get_report(
a_coverage_options => ut_coverage_options(
coverage_run_id => 'A6AA5B7361251CE6E053020011ACA055'
)
)
);
```

```sql
--SESSION 1 or SESSION2 2 or SESSION 3
-- run after calls in SESSION 1 & 2 are finshed
declare
l_results_cursor sys_refcursor;
begin
l_results_cursor := ut_coverage_html_reporter().get_report_cursor(
a_coverage_options => ut_coverage_options(
coverage_run_id => 'A6AA5B7361251CE6E053020011ACA055'
)
);
--fetch and process the cursor results
close l_results_cursor;
end;
/
```

Specification of parameters for `get_report` and `get_report_cursor`
```sql
function get_report(
a_coverage_options ut_coverage_options,
a_client_character_set varchar2 := null
) return ut_varchar2_rows pipelined
```

```sql
function get_report_cursor(
a_coverage_options ut_coverage_options,
a_client_character_set varchar2 := null
) return sys_refcursor
```
```sql
ut_coverage_options(
coverage_run_id raw,
schema_names ut_varchar2_rows := null,
exclude_objects ut_varchar2_rows := null,
include_objects ut_varchar2_rows := null,
file_mappings ut_file_mappings := null
);
```

The `a_client_character_set` is used to provide character set to the report. Coverage reports in XML and HTML format include this information to assure that HMTL/XML encoding tag is aligned with encoding of the report produced.
Use this parameter to provide encoding of your client application.

The `a_coverage_options` parameter is used to control the scope and formatting of data returned by report.

`ut_coverage_options` object accepts the following arguments

- `coverage_run_id` - identifier of coverage run to generate report for - data-type `RAW(32)`
- `schema_names` - optional - list of schema names to include in coverage report - data-type `UT_VARCHAR2_ROWS`
- `exclude_objects` - optional - list of object names to exclude from report - data-type `UT_VARCHAR2_ROWS`
- `include_objects` - optional - list of object names to gather coverage on - data-type `UT_VARCHAR2_ROWS`
- `file_mappings` - optional - list of schema names to gather coverage on - data-type `UT_FILE_MAPPINGS`

`coverage_run_id` parameter identifies a common coverage run. The valid value type for that parameter is RAW(32).
It is recommended to use `sys_guid()` to generate a common, unique identifier for a specific coverage run.
If the identifier is not unique, previous runs of coverage that used the same `coverage_run_id` will be aggregated to the resulting coverage report.

For details on the meaning of `schema_names`, `exclude_objects`, `include_objects`, `file_mappings` see sections above.
Note that data-types of include/exclude/schema lists are different when calling `ut.run` vs. calling `get_report/get_report_cursor`.

2 changes: 1 addition & 1 deletion examples/RunExpectations.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ set echo off
@@demo_expectations.pck

begin
ut_coverage.coverage_start();
ut_coverage.coverage_start(sys_guid());
ut_coverage.set_develop_mode(true);
ut.run();
ut_coverage.set_develop_mode(false);
Expand Down
78 changes: 30 additions & 48 deletions source/api/ut_runner.pkb
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,6 @@ create or replace package body ut_runner is
/**
* Private functions
*/
function to_ut_object_list(a_names ut_varchar2_list, a_schema_names ut_varchar2_rows) return ut_object_names is
l_result ut_object_names;
l_object_name ut_object_name;
begin
if a_names is not empty then
l_result := ut_object_names();
for i in 1 .. a_names.count loop
l_object_name := ut_object_name(a_names(i));
if l_object_name.owner is null then
for i in 1 .. cardinality(a_schema_names) loop
l_result.extend;
l_result(l_result.last) := ut_object_name(a_schema_names(i)||'.'||l_object_name.name);
end loop;
else
l_result.extend;
l_result(l_result.last) := l_object_name;
end if;
end loop;
end if;
return l_result;
end;

procedure finish_run(a_run ut_run, a_force_manual_rollback boolean) is
begin
Expand All @@ -51,7 +30,6 @@ create or replace package body ut_runner is
ut_compound_data_helper.cleanup_diff();
if not a_force_manual_rollback then
rollback;
ut_utils.cleanup_session_temp_tables;
end if;
end;

Expand Down Expand Up @@ -95,9 +73,7 @@ create or replace package body ut_runner is
) is
l_run ut_run;
l_coverage_schema_names ut_varchar2_rows;
l_exclude_object_names ut_object_names := ut_object_names();
l_include_object_names ut_object_names;
l_paths ut_varchar2_list := ut_varchar2_list();
l_paths ut_varchar2_list;
l_random_test_order_seed positive;
l_tags ut_varchar2_rows := ut_varchar2_rows();
begin
Expand All @@ -113,55 +89,51 @@ create or replace package body ut_runner is

ut_event_manager.trigger_event(ut_event_manager.gc_initialize);
ut_event_manager.trigger_event(ut_event_manager.gc_debug, ut_run_info());

if a_random_test_order_seed is not null then
l_random_test_order_seed := a_random_test_order_seed;
elsif a_random_test_order then
dbms_random.seed( to_char(systimestamp,'yyyyddmmhh24missffff') );
l_random_test_order_seed := trunc(dbms_random.value(1, 1000000000));
end if;
if a_paths is null or a_paths is empty or a_paths.count = 1 and a_paths(1) is null then

l_paths := ut_utils.filter_list(ut_utils.string_table_to_table(a_paths,','), '.+');
if l_paths is null or l_paths is empty then
l_paths := ut_varchar2_list(sys_context('userenv', 'current_schema'));
else
for i in 1..a_paths.count loop
l_paths := l_paths multiset union ut_utils.string_to_table(a_string => a_paths(i),a_delimiter => ',');
end loop;
end if;

begin
ut_expectation_processor.reset_invalidation_exception();
ut_utils.save_dbms_output_to_cache();

ut_console_reporter_base.set_color_enabled(a_color_console);

if a_coverage_schemes is not empty then
l_coverage_schema_names := ut_utils.convert_collection(a_coverage_schemes);
else
l_coverage_schema_names := ut_suite_manager.get_schema_names(l_paths);
end if;

if a_exclude_objects is not empty then
l_exclude_object_names := to_ut_object_list(a_exclude_objects, l_coverage_schema_names);
end if;


if a_tags is not null then
l_tags := l_tags multiset union distinct ut_utils.convert_collection(
l_tags := l_tags multiset union distinct ut_utils.convert_collection(
ut_utils.trim_list_elements(ut_utils.filter_list(ut_utils.string_to_table(a_tags,','),ut_utils.gc_word_no_space))
);
end if;
l_exclude_object_names := l_exclude_object_names multiset union all ut_suite_manager.get_schema_ut_packages(l_coverage_schema_names);

l_include_object_names := to_ut_object_list(a_include_objects, l_coverage_schema_names);

l_run := ut_run(
null,
l_paths,
l_coverage_schema_names,
l_exclude_object_names,
l_include_object_names,
set(a_source_file_mappings),
set(a_test_file_mappings),
a_client_character_set,
l_random_test_order_seed,
l_tags
a_run_paths => l_paths,
a_coverage_options => ut_coverage_options(
coverage_run_id => sys_guid(),
schema_names => l_coverage_schema_names,
exclude_objects => ut_utils.convert_collection(a_exclude_objects),
include_objects => ut_utils.convert_collection(a_include_objects),
file_mappings => set(a_source_file_mappings)
),
a_test_file_mappings => set(a_test_file_mappings),
a_client_character_set => a_client_character_set,
a_random_test_order_seed => l_random_test_order_seed,
a_run_tags => l_tags
);

ut_suite_manager.configure_execution_by_path(l_paths, l_run.items, l_random_test_order_seed, l_tags);
Expand Down Expand Up @@ -296,5 +268,15 @@ create or replace package body ut_runner is
return l_result;
end;

procedure coverage_start(a_coverage_run_id raw) is
begin
ut_coverage.coverage_start(a_coverage_run_id);
end;

procedure coverage_stop is
begin
ut_coverage.coverage_stop;
end;

end ut_runner;
/
4 changes: 4 additions & 0 deletions source/api/ut_runner.pks
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,9 @@ create or replace package ut_runner authid current_user is
*/
function hash_suite_path(a_path varchar2, a_random_seed positiven) return varchar2;

procedure coverage_start(a_coverage_run_id raw);

procedure coverage_stop;

end ut_runner;
/
Loading
0