Skip to content

Commit 0daab33

Browse files
committed
Checkpoint
1 parent 33f5485 commit 0daab33

File tree

12 files changed

+280
-71
lines changed

12 files changed

+280
-71
lines changed

docs/userguide/annotations.md

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,11 +1616,8 @@ or
16161616

16171617
Tags are defined as a comma separated list within the `--%tags` annotation.
16181618

1619-
When executing a test run with tag filter applied, the framework will find all tests associated with the given tags and execute them.
1620-
The framework applies `OR` logic to all specified tags so any test / suite that matches at least one tag will be included in the test run.
1621-
1622-
When a suite/context is tagged, all of its children will automatically inherit the tag and get executed along with the parent. Parent suite tests are not executed, but a suitepath hierarchy is kept.
1623-
1619+
When a suite/context is tagged, all of its children will automatically inherit the tag and get executed along with the parent, unless they are excluded by tag expression.
1620+
Parent suite tests are not executed, but a suitepath hierarchy is kept.
16241621

16251622
Sample test suite package with tags.
16261623
```sql linenums="1"
@@ -1661,7 +1658,37 @@ end ut_sample_test;
16611658
/
16621659
```
16631660

1664-
Execution of the test is done by using the parameter `a_tags`
1661+
#### Tag Expressions
1662+
1663+
Tag expressions are boolean expressions with the operators !, & and |. In addition, ( and ) can be used to adjust for operator precedence.
1664+
1665+
Two special expressions are supported, any() and none(), which select all tests with any tags at all, and all tests without any tags, respectively. These special expressions may be combined with other expressions just like normal tags.
1666+
1667+
| Operator | Meaning |
1668+
| -------- | --------|
1669+
| ! | not |
1670+
| & | and |
1671+
| \| | or |
1672+
1673+
If you are tagging your tests across multiple dimensions, tag expressions help you to select which tests to execute. When tagging by test type (e.g., micro, integration, end-to-end) and feature (e.g., product, catalog, shipping), the following tag expressions can be useful.
1674+
1675+
1676+
| Tag Expression | Selection |
1677+
| -------- | --------|
1678+
| product | all tests for product |
1679+
| catalog \| shipping | all tests for catalog plus all tests for shipping |
1680+
| catalog & shipping | all tests for the intersection between catalog and shipping |
1681+
| product & !end-to-end | all tests for product, but not the end-to-end tests |
1682+
| (micro \| integration) & (product \| shipping) | all micro or integration tests for product or shipping |
1683+
1684+
1685+
Execution of the test is done by using the parameter `a_tags` with tag expressions
1686+
1687+
1688+
```sql linenums="1"
1689+
select * from table(ut.run(a_tags => 'fast||!complex'));
1690+
```
1691+
The above call will execute all tests from `ut_sample_test` package as the whole suite is tagged with `api` because a suite meet expression condition.
16651692

16661693
```sql linenums="1"
16671694
select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'api'));
@@ -1683,8 +1710,14 @@ The above call will execute both `ut_sample_test.ut_refcursors1` and `ut_sample_
16831710
Tags must follow the below naming convention:
16841711

16851712
- tag is case sensitive
1686-
- tag can contain special characters like `$#/\?-!` etc.
1687-
- tag cannot be an empty string
1713+
- tag must not contain any of the following reserved characters:
1714+
- comma (,)
1715+
- left or right parenthesis ((, ))
1716+
- ampersand (&)
1717+
- vertical bar (|)
1718+
- exclamation point (!)
1719+
- tag cannot be null or blank
1720+
- tag cannot contain whitespace
16881721
- tag cannot start with a dash, e.g. `-some-stuff` is **not** a valid tag
16891722
- tag cannot contain spaces, e.g. `test of batch`. To create a multi-word tag use underscores or dashes, e.g. `test_of_batch`, `test-of-batch`
16901723
- leading and trailing spaces are ignored in tag name, e.g. `--%tags( tag1 , tag2 )` becomes `tag1` and `tag2` tag names
@@ -1693,13 +1726,31 @@ Tags must follow the below naming convention:
16931726
#### Excluding tests/suites by tags
16941727

16951728
It is possible to exclude parts of test suites with tags.
1696-
In order to do so, prefix the tag name to exclude with a `-` (dash) sign when invoking the test run.
1697-
1729+
In order to do so, prefix the tag name to exclude with a `!` (exclamation) sign when invoking the test run which is equivalent of `-` (dash) in legacy notation.
16981730
Examples (based on above sample test suite)
16991731

1732+
```sql linenums="1"
1733+
select * from table(ut.run(a_tags => '(api|fast)&!complex'));
1734+
```
1735+
1736+
which is equivalent of legacy calling:
1737+
17001738
```sql linenums="1"
17011739
select * from table(ut.run(a_tags => 'api,fast,-complex'));
17021740
```
1741+
1742+
or
1743+
1744+
```sql linenums="1"
1745+
select * from table(ut.run(a_tags => '(api|fast)&(!complex&!test1)'));
1746+
```
1747+
1748+
which is equivalent of legacy calling:
1749+
1750+
```sql linenums="1"
1751+
select * from table(ut.run(a_tags => 'api,fast,-complex,-test1'));
1752+
```
1753+
17031754
The above call will execute all suites/contexts/tests that are marked with any of tags `api` or `fast` except those suites/contexts/tests that are marked as `complex`.
17041755
Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed.
17051756

source/api/ut_runner.pkb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ create or replace package body ut_runner is
7979
l_coverage_schema_names ut_varchar2_rows;
8080
l_paths ut_varchar2_list;
8181
l_random_test_order_seed positive;
82-
l_tags ut_varchar2_rows := ut_varchar2_rows();
82+
l_tags varchar2(4000) := a_tags;
83+
8384
begin
8485
ut_event_manager.initialize();
8586
if a_reporters is not empty then
@@ -94,6 +95,11 @@ create or replace package body ut_runner is
9495
ut_event_manager.trigger_event(ut_event_manager.gc_initialize);
9596
ut_event_manager.trigger_event(ut_event_manager.gc_debug, ut_run_info());
9697

98+
--Verify tag tag expression is valid
99+
if regexp_like(l_tags,'[&|]{2,}|[!-]{2,}|[!-][&|]|[^-&|!]+[-!]|[-!|&][)]')
100+
or (regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)')) then
101+
raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression');
102+
end if;
97103
if a_random_test_order_seed is not null then
98104
l_random_test_order_seed := a_random_test_order_seed;
99105
elsif a_random_test_order then
@@ -118,12 +124,6 @@ create or replace package body ut_runner is
118124
l_coverage_schema_names := ut_suite_manager.get_schema_names(l_paths);
119125
end if;
120126

121-
122-
if a_tags is not null then
123-
l_tags := l_tags multiset union distinct ut_utils.convert_collection(
124-
ut_utils.trim_list_elements(ut_utils.filter_list(ut_utils.string_to_table(a_tags,','),ut_utils.gc_word_no_space))
125-
);
126-
end if;
127127
l_run := ut_run(
128128
a_run_paths => l_paths,
129129
a_coverage_options => ut_coverage_options(

source/core/types/ut_run.tpb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ create or replace type body ut_run as
2424
a_test_file_mappings ut_file_mappings := null,
2525
a_client_character_set varchar2 := null,
2626
a_random_test_order_seed positive := null,
27-
a_run_tags ut_varchar2_rows := null
27+
a_run_tags varchar2 := null
2828
) return self as result is
2929
begin
3030
self.run_paths := a_run_paths;

source/core/types/ut_run.tps

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
create or replace type ut_run under ut_suite_item (
1+
create or replace type ut_run force under ut_suite_item (
22
/*
33
utPLSQL - Version 3
44
Copyright 2016 - 2021 utPLSQL Project
@@ -21,7 +21,7 @@ create or replace type ut_run under ut_suite_item (
2121
project_name varchar2(4000),
2222
items ut_suite_items,
2323
run_paths ut_varchar2_list,
24-
run_tags ut_varchar2_rows,
24+
run_tags varchar2(4000),
2525
coverage_options ut_coverage_options,
2626
test_file_mappings ut_file_mappings,
2727
client_character_set varchar2(100),
@@ -34,7 +34,7 @@ create or replace type ut_run under ut_suite_item (
3434
a_test_file_mappings ut_file_mappings := null,
3535
a_client_character_set varchar2 := null,
3636
a_random_test_order_seed positive := null,
37-
a_run_tags ut_varchar2_rows := null
37+
a_run_tags varchar2 := null
3838
) return self as result,
3939
overriding member procedure mark_as_skipped(self in out nocopy ut_run,a_skip_reason in varchar2),
4040
overriding member function do_execute(self in out nocopy ut_run) return boolean,

source/core/ut_suite_builder.pkb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ create or replace package body ut_suite_builder is
205205
l_tag_items := ut_utils.trim_list_elements(ut_utils.string_to_table(a_tags_ann_text(l_annotation_pos),','));
206206
if l_tag_items is not empty then
207207
for i in 1 .. l_tag_items.count loop
208-
if regexp_like(l_tag_items(i),'^[^-](\S)+$') then
208+
if regexp_like(l_tag_items(i),'^[^-!&|](\S)+$') then
209209
l_tags_list.extend();
210210
l_tags_list(l_tags_list.last) := l_tag_items(i);
211211
else

source/core/ut_suite_cache_manager.pkb

Lines changed: 108 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -221,52 +221,118 @@ create or replace package body ut_suite_cache_manager is
221221
return l_suite_items;
222222
end;
223223

224+
/*
225+
To support a legact tag notation
226+
, = OR
227+
- = NOT
228+
we will perform a replace of that characters into
229+
new notation.
230+
|| = OR
231+
&& = AND
232+
^ = NOT
233+
*/
234+
--TODO: How do we prevent when old notation reach 4k an new will be longer?
235+
function replace_legacy_tag_notation(a_tags varchar2
236+
) return varchar2 is
237+
l_tags ut_varchar2_list := ut_utils.string_to_table(a_tags,',');
238+
l_tags_include varchar2(2000);
239+
l_tags_exclude varchar2(2000);
240+
l_return_tag varchar2(4000);
241+
begin
242+
select listagg( t.column_value,' | ')
243+
within group( order by column_value)
244+
into l_tags_include
245+
from table(l_tags) t
246+
where t.column_value not like '-%';
247+
248+
select listagg( replace(t.column_value,'-','!'),' & ')
249+
within group( order by column_value)
250+
into l_tags_exclude
251+
from table(l_tags) t
252+
where t.column_value like '-%';
253+
254+
l_return_tag:=
255+
case when l_tags_include is not null then
256+
'('||l_tags_include||')' else null end ||
257+
case when l_tags_include is not null and l_tags_exclude is not null then
258+
' & ' else null end ||
259+
case when l_tags_exclude is not null then
260+
'('||l_tags_exclude||')' else null end;
261+
262+
return l_return_tag;
263+
end;
264+
265+
function create_where_filter(a_tags varchar2
266+
) return varchar2 is
267+
l_tags varchar2(4000):= replace(a_tags,' ');
268+
begin
269+
if instr(l_tags,',') > 0 or instr(l_tags,'-') > 0 then
270+
l_tags := replace(replace_legacy_tag_notation(l_tags),' ');
271+
end if;
272+
l_tags := REGEXP_REPLACE(l_tags,
273+
'(\(|\)|\||\!|\&)?([^|&!-]+)(\(|\)|\||\!|\&)?',
274+
q'[\1q'<\2>' member of tags\3]');
275+
--replace operands to XPath
276+
l_tags := REGEXP_REPLACE(l_tags, '\|',' or ');
277+
l_tags := REGEXP_REPLACE(l_tags , '\&',' and ');
278+
l_tags := REGEXP_REPLACE(l_tags,q'[(\!)(q'<[^|&!-]+>')( member of tags)]','\2 not \3');
279+
l_tags := '('||l_tags||')';
280+
return l_tags;
281+
end;
282+
224283
/*
225284
Having a base set of suites we will do a further filter down if there are
226285
any tags defined.
227-
*/
286+
*/
228287
function get_tags_suites (
229288
a_suite_items ut_suite_cache_rows,
230-
a_tags ut_varchar2_rows
289+
a_tags varchar2
231290
) return ut_suite_cache_rows is
232-
l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows();
233-
l_include_tags ut_varchar2_rows;
234-
l_exclude_tags ut_varchar2_rows;
291+
l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows();
292+
l_sql varchar2(32000);
293+
l_tags varchar2(4000):= create_where_filter(a_tags);
235294
begin
236-
237-
select /*+ no_parallel */ column_value
238-
bulk collect into l_include_tags
239-
from table(a_tags)
240-
where column_value not like '-%';
241-
242-
select /*+ no_parallel */ ltrim(column_value,'-')
243-
bulk collect into l_exclude_tags
244-
from table(a_tags)
245-
where column_value like '-%';
246-
247-
with included_tags as (
248-
select c.path as path
249-
from table(a_suite_items) c
250-
where c.tags multiset intersect l_include_tags is not empty or l_include_tags is empty
251-
),
252-
excluded_tags as (
253-
select c.path as path
254-
from table(a_suite_items) c
255-
where c.tags multiset intersect l_exclude_tags is not empty
256-
)
257-
select value(c) as obj
258-
bulk collect into l_suite_tags
259-
from table(a_suite_items) c
260-
where exists (
261-
select 1 from included_tags t
262-
where t.path||'.' like c.path || '.%' /*all ancestors and self*/
263-
or c.path||'.' like t.path || '.%' /*all descendants and self*/
264-
)
265-
and not exists (
266-
select 1 from excluded_tags t
267-
where c.path||'.' like t.path || '.%' /*all descendants and self*/
268-
);
269-
return l_suite_tags;
295+
l_sql :=
296+
q'[
297+
with
298+
suites_mv as (
299+
select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags
300+
from table(:suite_items) c
301+
),
302+
suites_matching_expr as (
303+
select c.id,c.path as path,c.self_type,c.object_owner,c.tags
304+
from suites_mv c
305+
where c.self_type in ('UT_SUITE','UT_CONTEXT')
306+
and ]'||l_tags||q'[
307+
),
308+
tests_matching_expr as (
309+
select c.id,c.path as path,c.self_type,c.object_owner,c.tags
310+
from suites_mv c where c.self_type in ('UT_TEST')
311+
and ]'||l_tags||q'[
312+
),
313+
tests_with_tags_inherited_from_suite as (
314+
select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner
315+
from suites_mv c join suites_matching_expr t
316+
on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner)
317+
),
318+
tests_with_tags_promoted_to_suites as (
319+
select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner
320+
from suites_mv c join tests_matching_expr t
321+
on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner)
322+
)
323+
select obj from suites_mv c,
324+
(select id,row_number() over (partition by id order by id) r_num from
325+
(select id
326+
from tests_with_tags_promoted_to_suites tst
327+
where ]'||l_tags||q'[
328+
union all
329+
select id from tests_with_tags_inherited_from_suite tst
330+
where ]'||l_tags||q'[
331+
)
332+
) t where c.id = t.id and r_num = 1 ]';
333+
334+
execute immediate l_sql bulk collect into l_suite_tags using a_suite_items;
335+
return l_suite_tags;
270336
end;
271337

272338
/*
@@ -323,17 +389,17 @@ create or replace package body ut_suite_cache_manager is
323389
function get_cached_suite_rows(
324390
a_schema_paths ut_path_items,
325391
a_random_seed positive := null,
326-
a_tags ut_varchar2_rows := null
392+
a_tags varchar2 := null
327393
) return ut_suite_cache_rows is
328394
l_results ut_suite_cache_rows := ut_suite_cache_rows();
329395
l_suite_items ut_suite_cache_rows := ut_suite_cache_rows();
330396
l_schema_paths ut_path_items;
331-
l_tags ut_varchar2_rows := coalesce(a_tags,ut_varchar2_rows());
397+
l_tags varchar2(4000) := a_tags;
332398
begin
333399

334400
l_schema_paths := a_schema_paths;
335401
l_suite_items := get_suite_items(a_schema_paths);
336-
if l_tags.count > 0 then
402+
if length(l_tags) > 0 then
337403
l_suite_items := get_tags_suites(l_suite_items,l_tags);
338404
end if;
339405

source/core/ut_suite_cache_manager.pks

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ create or replace package ut_suite_cache_manager authid definer is
5757
function get_cached_suite_rows(
5858
a_schema_paths ut_path_items,
5959
a_random_seed positive := null,
60-
a_tags ut_varchar2_rows := null
60+
a_tags varchar2 := null
6161
) return ut_suite_cache_rows;
6262

6363
function get_schema_paths(a_paths in ut_varchar2_list) return ut_path_items;
@@ -95,5 +95,8 @@ create or replace package ut_suite_cache_manager authid definer is
9595
a_procedure_name varchar2
9696
) return boolean;
9797

98+
99+
function create_where_filter(a_tags varchar2
100+
) return varchar2;
98101
end ut_suite_cache_manager;
99102
/

0 commit comments

Comments
 (0)