From 0daab3389dbf3fd0a0ec28e800479cfff486d45c Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 27 Mar 2023 20:37:09 -0700 Subject: [PATCH 01/34] Checkpoint --- docs/userguide/annotations.md | 71 ++++++++++-- source/api/ut_runner.pkb | 14 +-- source/core/types/ut_run.tpb | 2 +- source/core/types/ut_run.tps | 6 +- source/core/ut_suite_builder.pkb | 2 +- source/core/ut_suite_cache_manager.pkb | 150 ++++++++++++++++++------- source/core/ut_suite_cache_manager.pks | 5 +- source/core/ut_suite_manager.pkb | 6 +- source/core/ut_suite_manager.pks | 2 +- source/core/ut_utils.pks | 4 + test/ut3_user/api/test_ut_run.pkb | 74 +++++++++++- test/ut3_user/api/test_ut_run.pks | 15 +++ 12 files changed, 280 insertions(+), 71 deletions(-) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 0e3b6446e..7fd2c09fa 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1616,11 +1616,8 @@ or Tags are defined as a comma separated list within the `--%tags` annotation. -When executing a test run with tag filter applied, the framework will find all tests associated with the given tags and execute them. -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. - -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. - +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. +Parent suite tests are not executed, but a suitepath hierarchy is kept. Sample test suite package with tags. ```sql linenums="1" @@ -1661,7 +1658,37 @@ end ut_sample_test; / ``` -Execution of the test is done by using the parameter `a_tags` +#### Tag Expressions + +Tag expressions are boolean expressions with the operators !, & and |. In addition, ( and ) can be used to adjust for operator precedence. + +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. + +| Operator | Meaning | +| -------- | --------| +| ! | not | +| & | and | +| \| | or | + +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. + + +| Tag Expression | Selection | +| -------- | --------| +| product | all tests for product | +| catalog \| shipping | all tests for catalog plus all tests for shipping | +| catalog & shipping | all tests for the intersection between catalog and shipping | +| product & !end-to-end | all tests for product, but not the end-to-end tests | +| (micro \| integration) & (product \| shipping) | all micro or integration tests for product or shipping | + + +Execution of the test is done by using the parameter `a_tags` with tag expressions + + +```sql linenums="1" +select * from table(ut.run(a_tags => 'fast||!complex')); +``` +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. ```sql linenums="1" 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_ Tags must follow the below naming convention: - tag is case sensitive -- tag can contain special characters like `$#/\?-!` etc. -- tag cannot be an empty string +- tag must not contain any of the following reserved characters: + - comma (,) + - left or right parenthesis ((, )) + - ampersand (&) + - vertical bar (|) + - exclamation point (!) +- tag cannot be null or blank +- tag cannot contain whitespace - tag cannot start with a dash, e.g. `-some-stuff` is **not** a valid tag - 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` - 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: #### Excluding tests/suites by tags It is possible to exclude parts of test suites with tags. -In order to do so, prefix the tag name to exclude with a `-` (dash) sign when invoking the test run. - +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. Examples (based on above sample test suite) +```sql linenums="1" +select * from table(ut.run(a_tags => '(api|fast)&!complex')); +``` + +which is equivalent of legacy calling: + ```sql linenums="1" select * from table(ut.run(a_tags => 'api,fast,-complex')); ``` + +or + +```sql linenums="1" +select * from table(ut.run(a_tags => '(api|fast)&(!complex&!test1)')); +``` + +which is equivalent of legacy calling: + +```sql linenums="1" +select * from table(ut.run(a_tags => 'api,fast,-complex,-test1')); +``` + 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`. Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index b69a51a04..2760868e0 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -79,7 +79,8 @@ create or replace package body ut_runner is l_coverage_schema_names ut_varchar2_rows; l_paths ut_varchar2_list; l_random_test_order_seed positive; - l_tags ut_varchar2_rows := ut_varchar2_rows(); + l_tags varchar2(4000) := a_tags; + begin ut_event_manager.initialize(); if a_reporters is not empty then @@ -94,6 +95,11 @@ 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()); + --Verify tag tag expression is valid + if regexp_like(l_tags,'[&|]{2,}|[!-]{2,}|[!-][&|]|[^-&|!]+[-!]|[-!|&][)]') + or (regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)')) then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + end if; 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 @@ -118,12 +124,6 @@ create or replace package body ut_runner is l_coverage_schema_names := ut_suite_manager.get_schema_names(l_paths); end if; - - if a_tags is not null then - 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_run := ut_run( a_run_paths => l_paths, a_coverage_options => ut_coverage_options( diff --git a/source/core/types/ut_run.tpb b/source/core/types/ut_run.tpb index cdafb30fc..660c88791 100644 --- a/source/core/types/ut_run.tpb +++ b/source/core/types/ut_run.tpb @@ -24,7 +24,7 @@ create or replace type body ut_run as a_test_file_mappings ut_file_mappings := null, a_client_character_set varchar2 := null, a_random_test_order_seed positive := null, - a_run_tags ut_varchar2_rows := null + a_run_tags varchar2 := null ) return self as result is begin self.run_paths := a_run_paths; diff --git a/source/core/types/ut_run.tps b/source/core/types/ut_run.tps index debf847cd..8ce80d2a1 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 - 2021 utPLSQL Project @@ -21,7 +21,7 @@ create or replace type ut_run under ut_suite_item ( project_name varchar2(4000), items ut_suite_items, run_paths ut_varchar2_list, - run_tags ut_varchar2_rows, + run_tags varchar2(4000), coverage_options ut_coverage_options, test_file_mappings ut_file_mappings, client_character_set varchar2(100), @@ -34,7 +34,7 @@ create or replace type ut_run under ut_suite_item ( a_test_file_mappings ut_file_mappings := null, a_client_character_set varchar2 := null, a_random_test_order_seed positive := null, - a_run_tags ut_varchar2_rows := null + a_run_tags varchar2 := null ) return self as result, overriding member procedure mark_as_skipped(self in out nocopy ut_run,a_skip_reason in varchar2), overriding member function do_execute(self in out nocopy ut_run) return boolean, diff --git a/source/core/ut_suite_builder.pkb b/source/core/ut_suite_builder.pkb index ebb113370..4aa2ff915 100644 --- a/source/core/ut_suite_builder.pkb +++ b/source/core/ut_suite_builder.pkb @@ -205,7 +205,7 @@ create or replace package body ut_suite_builder is l_tag_items := ut_utils.trim_list_elements(ut_utils.string_to_table(a_tags_ann_text(l_annotation_pos),',')); if l_tag_items is not empty then for i in 1 .. l_tag_items.count loop - if regexp_like(l_tag_items(i),'^[^-](\S)+$') then + if regexp_like(l_tag_items(i),'^[^-!&|](\S)+$') then l_tags_list.extend(); l_tags_list(l_tags_list.last) := l_tag_items(i); else diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index d3e832a9b..276f9427d 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -221,52 +221,118 @@ create or replace package body ut_suite_cache_manager is return l_suite_items; end; + /* + To support a legact tag notation + , = OR + - = NOT + we will perform a replace of that characters into + new notation. + || = OR + && = AND + ^ = NOT + */ + --TODO: How do we prevent when old notation reach 4k an new will be longer? + function replace_legacy_tag_notation(a_tags varchar2 + ) return varchar2 is + l_tags ut_varchar2_list := ut_utils.string_to_table(a_tags,','); + l_tags_include varchar2(2000); + l_tags_exclude varchar2(2000); + l_return_tag varchar2(4000); + begin + select listagg( t.column_value,' | ') + within group( order by column_value) + into l_tags_include + from table(l_tags) t + where t.column_value not like '-%'; + + select listagg( replace(t.column_value,'-','!'),' & ') + within group( order by column_value) + into l_tags_exclude + from table(l_tags) t + where t.column_value like '-%'; + + l_return_tag:= + case when l_tags_include is not null then + '('||l_tags_include||')' else null end || + case when l_tags_include is not null and l_tags_exclude is not null then + ' & ' else null end || + case when l_tags_exclude is not null then + '('||l_tags_exclude||')' else null end; + + return l_return_tag; + end; + + function create_where_filter(a_tags varchar2 + ) return varchar2 is + l_tags varchar2(4000):= replace(a_tags,' '); + begin + if instr(l_tags,',') > 0 or instr(l_tags,'-') > 0 then + l_tags := replace(replace_legacy_tag_notation(l_tags),' '); + end if; + l_tags := REGEXP_REPLACE(l_tags, + '(\(|\)|\||\!|\&)?([^|&!-]+)(\(|\)|\||\!|\&)?', + q'[\1q'<\2>' member of tags\3]'); + --replace operands to XPath + l_tags := REGEXP_REPLACE(l_tags, '\|',' or '); + l_tags := REGEXP_REPLACE(l_tags , '\&',' and '); + l_tags := REGEXP_REPLACE(l_tags,q'[(\!)(q'<[^|&!-]+>')( member of tags)]','\2 not \3'); + l_tags := '('||l_tags||')'; + return l_tags; + end; + /* Having a base set of suites we will do a further filter down if there are any tags defined. - */ + */ function get_tags_suites ( a_suite_items ut_suite_cache_rows, - a_tags ut_varchar2_rows + a_tags varchar2 ) return ut_suite_cache_rows is - l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows(); - l_include_tags ut_varchar2_rows; - l_exclude_tags ut_varchar2_rows; + l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows(); + l_sql varchar2(32000); + l_tags varchar2(4000):= create_where_filter(a_tags); begin - - select /*+ no_parallel */ column_value - bulk collect into l_include_tags - from table(a_tags) - where column_value not like '-%'; - - select /*+ no_parallel */ ltrim(column_value,'-') - bulk collect into l_exclude_tags - from table(a_tags) - where column_value like '-%'; - - with included_tags as ( - select c.path as path - from table(a_suite_items) c - where c.tags multiset intersect l_include_tags is not empty or l_include_tags is empty - ), - excluded_tags as ( - select c.path as path - from table(a_suite_items) c - where c.tags multiset intersect l_exclude_tags is not empty - ) - select value(c) as obj - bulk collect into l_suite_tags - from table(a_suite_items) c - where exists ( - select 1 from included_tags t - where t.path||'.' like c.path || '.%' /*all ancestors and self*/ - or c.path||'.' like t.path || '.%' /*all descendants and self*/ - ) - and not exists ( - select 1 from excluded_tags t - where c.path||'.' like t.path || '.%' /*all descendants and self*/ - ); - return l_suite_tags; + l_sql := + q'[ +with + suites_mv as ( + select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags + from table(:suite_items) c + ), + suites_matching_expr as ( + select c.id,c.path as path,c.self_type,c.object_owner,c.tags + from suites_mv c + where c.self_type in ('UT_SUITE','UT_CONTEXT') + and ]'||l_tags||q'[ + ), + tests_matching_expr as ( + select c.id,c.path as path,c.self_type,c.object_owner,c.tags + from suites_mv c where c.self_type in ('UT_TEST') + and ]'||l_tags||q'[ + ), + tests_with_tags_inherited_from_suite as ( + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner + from suites_mv c join suites_matching_expr t + on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner) + ), + tests_with_tags_promoted_to_suites as ( + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner + from suites_mv c join tests_matching_expr t + on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner) + ) + select obj from suites_mv c, + (select id,row_number() over (partition by id order by id) r_num from + (select id + from tests_with_tags_promoted_to_suites tst + where ]'||l_tags||q'[ + union all + select id from tests_with_tags_inherited_from_suite tst + where ]'||l_tags||q'[ + ) + ) t where c.id = t.id and r_num = 1 ]'; + + execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; + return l_suite_tags; end; /* @@ -323,17 +389,17 @@ create or replace package body ut_suite_cache_manager is function get_cached_suite_rows( a_schema_paths ut_path_items, a_random_seed positive := null, - a_tags ut_varchar2_rows := null + a_tags varchar2 := null ) return ut_suite_cache_rows is l_results ut_suite_cache_rows := ut_suite_cache_rows(); l_suite_items ut_suite_cache_rows := ut_suite_cache_rows(); l_schema_paths ut_path_items; - l_tags ut_varchar2_rows := coalesce(a_tags,ut_varchar2_rows()); + l_tags varchar2(4000) := a_tags; begin l_schema_paths := a_schema_paths; l_suite_items := get_suite_items(a_schema_paths); - if l_tags.count > 0 then + if length(l_tags) > 0 then l_suite_items := get_tags_suites(l_suite_items,l_tags); end if; diff --git a/source/core/ut_suite_cache_manager.pks b/source/core/ut_suite_cache_manager.pks index 974babca5..72dd08800 100644 --- a/source/core/ut_suite_cache_manager.pks +++ b/source/core/ut_suite_cache_manager.pks @@ -57,7 +57,7 @@ create or replace package ut_suite_cache_manager authid definer is function get_cached_suite_rows( a_schema_paths ut_path_items, a_random_seed positive := null, - a_tags ut_varchar2_rows := null + a_tags varchar2 := null ) return ut_suite_cache_rows; 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 a_procedure_name varchar2 ) return boolean; + +function create_where_filter(a_tags varchar2 + ) return varchar2; end ut_suite_cache_manager; / diff --git a/source/core/ut_suite_manager.pkb b/source/core/ut_suite_manager.pkb index 86f68c076..0f8629ded 100644 --- a/source/core/ut_suite_manager.pkb +++ b/source/core/ut_suite_manager.pkb @@ -352,7 +352,7 @@ create or replace package body ut_suite_manager is function get_cached_suite_data( a_schema_paths ut_path_items, a_random_seed positive, - a_tags ut_varchar2_rows := null, + a_tags varchar2 := null, a_skip_all_objects boolean := false ) return t_cached_suites_cursor is l_unfiltered_rows ut_suite_cache_rows; @@ -451,7 +451,7 @@ create or replace package body ut_suite_manager is a_schema_paths ut_path_items, a_suites in out nocopy ut_suite_items, a_random_seed positive, - a_tags ut_varchar2_rows := null + a_tags varchar2 := null ) is begin reconstruct_from_cache( @@ -528,7 +528,7 @@ create or replace package body ut_suite_manager is a_paths ut_varchar2_list, a_suites out nocopy ut_suite_items, a_random_seed positive := null, - a_tags ut_varchar2_rows := ut_varchar2_rows() + a_tags varchar2 := null ) is l_paths ut_varchar2_list := a_paths; l_schema_names ut_varchar2_rows; diff --git a/source/core/ut_suite_manager.pks b/source/core/ut_suite_manager.pks index 65b3c0654..07539139a 100644 --- a/source/core/ut_suite_manager.pks +++ b/source/core/ut_suite_manager.pks @@ -53,7 +53,7 @@ create or replace package ut_suite_manager authid current_user is a_paths in ut_varchar2_list, a_suites out nocopy ut_suite_items, a_random_seed in positive := null, - a_tags ut_varchar2_rows := ut_varchar2_rows() + a_tags in varchar2 := null ); /** diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index 18a3d5952..f7f7b4f00 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -121,6 +121,10 @@ create or replace package ut_utils authid definer is ex_failed_open_cur exception; gc_failed_open_cur constant pls_integer := -20218; pragma exception_init (ex_failed_open_cur, -20218); + + ex_invalid_tag_expression exception; + gc_invalid_tag_expression constant pls_integer := -20219; + pragma exception_init (ex_invalid_tag_expression, -20219); gc_max_storage_varchar2_len constant integer := 4000; gc_max_output_string_length constant integer := 4000; diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 41b969359..6f6e229c8 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -948,7 +948,7 @@ Failures:% procedure two_test_run_by_two_tags is l_results clob; begin - ut3_tester_helper.run_helper.run(a_tags => 'subtest1,subtest2'); + ut3_tester_helper.run_helper.run(a_tags => 'subtest1|subtest2'); l_results := ut3_tester_helper.main_helper.get_dbms_output_as_clob(); --Assert ut.expect( l_results ).to_be_like( '%test_package_1%' ); @@ -958,7 +958,21 @@ Failures:% ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); end; - + + procedure two_test_run_by_two_tags_leg is + l_results clob; + begin + ut3_tester_helper.run_helper.run(a_tags => 'subtest1,subtest2'); + l_results := ut3_tester_helper.main_helper.get_dbms_output_as_clob(); + --Assert + ut.expect( l_results ).to_be_like( '%test_package_1%' ); + ut.expect( l_results ).to_be_like( '%test_package_2%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_1.test2%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_2.test2%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); + ut.expect( l_results ).not_to_be_like( '%test_package_3%' ); + end; + procedure suite_with_children_tag is l_results clob; begin @@ -1078,6 +1092,20 @@ Failures:% procedure tag_run_func_path_list is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => 'suite1test1|suite2test1'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3%' ); + end; + + procedure tag_run_func_path_list_leg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => 'suite1test1,suite2test1'); --Assert @@ -1092,6 +1120,18 @@ Failures:% procedure tag_inc_exc_run_func_path_list is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => '(suite1test1|suite2test1)&!suite2'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3%' ); + end; + + procedure tag_inc_exc_run_fun_pth_lst_lg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests.test_package_1',':tests'),a_tags => 'suite1test1,suite2test1,-suite2'); --Assert @@ -1104,6 +1144,22 @@ Failures:% procedure tag_exclude_run_func_path_list is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests,:tests2'),a_tags => '!suite1test2&!suite2test1&!test1suite3'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_3%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_3.test2%executed%' ); + end; + +procedure tag_exclude_run_fun_pth_lst_lg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(ut3_develop.ut_varchar2_list(':tests,:tests2'),a_tags => '-suite1test2,-suite2test1,-test1suite3'); --Assert @@ -1120,6 +1176,20 @@ Failures:% procedure tag_include_exclude_run_func is l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(a_tags => '(suite1)&(!suite1test2&!suite2test1&!test1suite3)'); + --Assert + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test2%executed%' ); + end; + + procedure tag_include_exclude_run_fun_lg is + l_results ut3_develop.ut_varchar2_list; begin l_results := ut3_tester_helper.run_helper.run(a_tags => 'suite1,-suite1test2,-suite2test1,-test1suite3'); --Assert diff --git a/test/ut3_user/api/test_ut_run.pks b/test/ut3_user/api/test_ut_run.pks index b3be8700d..db02ee8f8 100644 --- a/test/ut3_user/api/test_ut_run.pks +++ b/test/ut3_user/api/test_ut_run.pks @@ -202,6 +202,9 @@ create or replace package test_ut_run is --%test(Execute tests by passing two tags) procedure two_test_run_by_two_tags; + --%test(Execute tests by passing two tags - Legacy notation) + procedure two_test_run_by_two_tags_leg; + --%test(Execute suite and all of its children) procedure suite_with_children_tag; @@ -235,15 +238,27 @@ create or replace package test_ut_run is --%test(Runs tests from given paths with paths list and a tag) procedure tag_run_func_path_list; + --%test(Runs tests from given paths with paths list and a tag - Legacy Notation) + procedure tag_run_func_path_list_leg; + --%test(Runs tests from given paths with paths list and include/exclude tags) procedure tag_inc_exc_run_func_path_list; + --%test(Runs tests from given paths with paths list and include/exclude tags - Legacy Notation) + procedure tag_inc_exc_run_fun_pth_lst_lg; + --%test(Runs tests from given path and excludes specific tags) procedure tag_exclude_run_func_path_list; + --%test(Runs tests from given path and excludes specific tags - Legacy Notation) + procedure tag_exclude_run_fun_pth_lst_lg; + --%test(Runs tests from given tags and exclude tags) procedure tag_include_exclude_run_func; + --%test(Runs tests from given tags and exclude tags - Legacy Notation) + procedure tag_include_exclude_run_fun_lg; + --%endcontext --%context(ut3_info context) From adbc76eb027f8425975b8277d4a10e2ec9ce7f7c Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 27 Mar 2023 23:04:34 -0700 Subject: [PATCH 02/34] Address too long identified in 11g. Address flaky expression checker to be reworked. --- source/api/ut_runner.pkb | 6 ++++-- source/core/ut_suite_cache_manager.pkb | 10 +++++----- source/core/ut_suite_cache_manager.pks | 5 +---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index 2760868e0..94538eb69 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -95,11 +95,13 @@ 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()); - --Verify tag tag expression is valid - if regexp_like(l_tags,'[&|]{2,}|[!-]{2,}|[!-][&|]|[^-&|!]+[-!]|[-!|&][)]') + --TODO:Verify tag tag expression is valid + /* + if regexp_like(l_tags,'[&|]{2,}|[!-]{2,}|[!-][&|]|[^-&|!,]+[-!]|[-!|&][)]') or (regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)')) then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end if; + */ 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 diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index 276f9427d..ff18deabf 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -270,7 +270,7 @@ create or replace package body ut_suite_cache_manager is l_tags := replace(replace_legacy_tag_notation(l_tags),' '); end if; l_tags := REGEXP_REPLACE(l_tags, - '(\(|\)|\||\!|\&)?([^|&!-]+)(\(|\)|\||\!|\&)?', + '(\(|\)|\||\!|\&)?([^|&!-()]+)(\(|\)|\||\!|\&)?', q'[\1q'<\2>' member of tags\3]'); --replace operands to XPath l_tags := REGEXP_REPLACE(l_tags, '\|',' or '); @@ -310,12 +310,12 @@ with from suites_mv c where c.self_type in ('UT_TEST') and ]'||l_tags||q'[ ), - tests_with_tags_inherited_from_suite as ( + tests_with_tags_inh_from_suite as ( select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner from suites_mv c join suites_matching_expr t on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner) ), - tests_with_tags_promoted_to_suites as ( + tests_with_tags_prom_to_suite as ( select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner from suites_mv c join tests_matching_expr t on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner) @@ -323,10 +323,10 @@ with select obj from suites_mv c, (select id,row_number() over (partition by id order by id) r_num from (select id - from tests_with_tags_promoted_to_suites tst + from tests_with_tags_prom_to_suite tst where ]'||l_tags||q'[ union all - select id from tests_with_tags_inherited_from_suite tst + select id from tests_with_tags_inh_from_suite tst where ]'||l_tags||q'[ ) ) t where c.id = t.id and r_num = 1 ]'; diff --git a/source/core/ut_suite_cache_manager.pks b/source/core/ut_suite_cache_manager.pks index 72dd08800..7f06e95eb 100644 --- a/source/core/ut_suite_cache_manager.pks +++ b/source/core/ut_suite_cache_manager.pks @@ -94,9 +94,6 @@ create or replace package ut_suite_cache_manager authid definer is a_package_name varchar2, a_procedure_name varchar2 ) return boolean; - - -function create_where_filter(a_tags varchar2 - ) return varchar2; + end ut_suite_cache_manager; / From 1478b0d21035398f3b6eccf82b37fa91adeb0e6b Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Wed, 29 Mar 2023 12:41:32 -0700 Subject: [PATCH 03/34] Adding validation for tag expression --- source/api/ut_runner.pkb | 7 +-- source/core/ut_utils.pkb | 63 +++++++++++++++++++++++++- source/core/ut_utils.pks | 2 + test/ut3_tester/core/test_ut_utils.pkb | 27 +++++++++++ test/ut3_tester/core/test_ut_utils.pks | 2 + 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index 94538eb69..1ecaf1a42 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -95,13 +95,10 @@ 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()); - --TODO:Verify tag tag expression is valid - /* - if regexp_like(l_tags,'[&|]{2,}|[!-]{2,}|[!-][&|]|[^-&|!,]+[-!]|[-!|&][)]') - or (regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)')) then + if ut_utils.valid_tag_expression(l_tags) = 0 then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end if; - */ + 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 diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 162e50f8f..03ce1ad55 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -988,7 +988,68 @@ create or replace package body ut_utils is return l_result; end; - + + function valid_tag_expression(a_tags in varchar2) return number is + t_left_side ut_varchar2_list := ut_varchar2_list('|','&',','); + t_right_side ut_varchar2_list := ut_varchar2_list('!','-'); + l_left_side_expression varchar2(100) := '[|&,]'; + l_left_side_regex varchar(400) := '([^|&,]*)[|&,](.*)'; + l_left_side varchar2(4000); + + l_rigth_side_expression varchar2(100) := '[!-]'; + l_right_side_regex varchar(400) := '([!-])([^!-].*)'; + l_right_side varchar2(4000); + + l_tags varchar2(4000) := a_tags; + l_result number :=1; + begin + --Validate that we have closed up all brackets + if regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)') then + l_result := 0; + end if; + + --Remove brackets as we dont evaluate expression only validate. + l_tags := replace(replace(l_tags,'('),')'); + + --Check if there are any left side operators for first in order from left to right + if regexp_count(l_tags,l_left_side_expression) > 0 then + --Extract left part of operator and remaining of string to right + l_left_side := regexp_replace(l_tags,l_left_side_regex,'\1'); + l_right_side := regexp_replace(l_tags,l_left_side_regex,'\2'); + + --If left side is null that means that we used left side operator without + -- left and right e.g. &test + if l_left_side is null then + l_result := 0; + else + --Extract right side from left side expression if there is any !- + --Remove first negation tag to see if there is double negation + l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); + end if; + + + --check that on right side there is no extra negation + if regexp_count(l_left_side,l_rigth_side_expression) > 0 then + l_result := 0; + end if; + + --Now process right side of string + if l_right_side is not null then + l_result := least(l_result,valid_tag_expression(l_right_side)); + else + l_result := 0; + end if; + else + --We just process single tag. + l_left_side := l_tags; + l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); + if regexp_count(l_left_side,l_rigth_side_expression) > 0 then + l_result := 0; + end if; + end if; + + return l_result; + end; end ut_utils; / diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index f7f7b4f00..2975c3846 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -477,5 +477,7 @@ create or replace package ut_utils authid definer is */ function interval_to_text(a_interval yminterval_unconstrained) return varchar2; + function valid_tag_expression(a_tags in varchar2) return number; + end ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 4ed718777..52c9cdff8 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -489,5 +489,32 @@ end; ut.expect(l_expected).to_equal(l_actual); end; + procedure valid_tag_expressions is + begin + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|!tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&!tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1|!tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2')); + + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,-tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1,-tag2')); + + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1|!tag2)|tag3')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1&!tag2)|(tag3&tag4)')); + + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('&!tag2')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!!tag1|!tag2')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2|')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('((!tag1|!tag2)|tag3')); + + end; + end test_ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 4d83b5042..b87d3b2c6 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -154,6 +154,8 @@ create or replace package test_ut_utils is --%test(returns text representation of interval year to month for custom interval) procedure int_conv_ym_date; + --%test(Test to validate different type of expressions passed as tags) + procedure valid_tag_expressions; --%endcontext From b5ad7475b53f5b4b14d9805f9c56b8e36dde0883 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Wed, 29 Mar 2023 14:53:24 -0700 Subject: [PATCH 04/34] Comment out to see why its failing. --- source/api/ut_runner.pkb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index 1ecaf1a42..5f4912a24 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -94,10 +94,12 @@ 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 ut_utils.valid_tag_expression(l_tags) = 0 then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end if; + */ if a_random_test_order_seed is not null then l_random_test_order_seed := a_random_test_order_seed; From 06cb054aa72b8aca8115f277e673485321efc0ad Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Wed, 29 Mar 2023 15:10:34 -0700 Subject: [PATCH 05/34] Revert "Comment out to see why its failing." This reverts commit b5ad7475b53f5b4b14d9805f9c56b8e36dde0883. Revert "Adding validation for tag expression" This reverts commit 1478b0d21035398f3b6eccf82b37fa91adeb0e6b. --- source/api/ut_runner.pkb | 7 +-- source/core/ut_utils.pkb | 63 +------------------------- source/core/ut_utils.pks | 2 - test/ut3_tester/core/test_ut_utils.pkb | 27 ----------- test/ut3_tester/core/test_ut_utils.pks | 2 - 5 files changed, 5 insertions(+), 96 deletions(-) diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index 5f4912a24..94538eb69 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -94,13 +94,14 @@ 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()); - + + --TODO:Verify tag tag expression is valid /* - if ut_utils.valid_tag_expression(l_tags) = 0 then + if regexp_like(l_tags,'[&|]{2,}|[!-]{2,}|[!-][&|]|[^-&|!,]+[-!]|[-!|&][)]') + or (regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)')) then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end if; */ - 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 diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 03ce1ad55..162e50f8f 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -988,68 +988,7 @@ create or replace package body ut_utils is return l_result; end; - - function valid_tag_expression(a_tags in varchar2) return number is - t_left_side ut_varchar2_list := ut_varchar2_list('|','&',','); - t_right_side ut_varchar2_list := ut_varchar2_list('!','-'); - l_left_side_expression varchar2(100) := '[|&,]'; - l_left_side_regex varchar(400) := '([^|&,]*)[|&,](.*)'; - l_left_side varchar2(4000); - - l_rigth_side_expression varchar2(100) := '[!-]'; - l_right_side_regex varchar(400) := '([!-])([^!-].*)'; - l_right_side varchar2(4000); - - l_tags varchar2(4000) := a_tags; - l_result number :=1; - begin - --Validate that we have closed up all brackets - if regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)') then - l_result := 0; - end if; - - --Remove brackets as we dont evaluate expression only validate. - l_tags := replace(replace(l_tags,'('),')'); - - --Check if there are any left side operators for first in order from left to right - if regexp_count(l_tags,l_left_side_expression) > 0 then - --Extract left part of operator and remaining of string to right - l_left_side := regexp_replace(l_tags,l_left_side_regex,'\1'); - l_right_side := regexp_replace(l_tags,l_left_side_regex,'\2'); - - --If left side is null that means that we used left side operator without - -- left and right e.g. &test - if l_left_side is null then - l_result := 0; - else - --Extract right side from left side expression if there is any !- - --Remove first negation tag to see if there is double negation - l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); - end if; - - - --check that on right side there is no extra negation - if regexp_count(l_left_side,l_rigth_side_expression) > 0 then - l_result := 0; - end if; - - --Now process right side of string - if l_right_side is not null then - l_result := least(l_result,valid_tag_expression(l_right_side)); - else - l_result := 0; - end if; - else - --We just process single tag. - l_left_side := l_tags; - l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); - if regexp_count(l_left_side,l_rigth_side_expression) > 0 then - l_result := 0; - end if; - end if; - - return l_result; - end; + end ut_utils; / diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index 2975c3846..f7f7b4f00 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -477,7 +477,5 @@ create or replace package ut_utils authid definer is */ function interval_to_text(a_interval yminterval_unconstrained) return varchar2; - function valid_tag_expression(a_tags in varchar2) return number; - end ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 52c9cdff8..4ed718777 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -489,32 +489,5 @@ end; ut.expect(l_expected).to_equal(l_actual); end; - procedure valid_tag_expressions is - begin - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|!tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&!tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1|!tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2')); - - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,-tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1,-tag2')); - - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1|!tag2)|tag3')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1&!tag2)|(tag3&tag4)')); - - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('&!tag2')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!!tag1|!tag2')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2|')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('((!tag1|!tag2)|tag3')); - - end; - end test_ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index b87d3b2c6..4d83b5042 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -154,8 +154,6 @@ create or replace package test_ut_utils is --%test(returns text representation of interval year to month for custom interval) procedure int_conv_ym_date; - --%test(Test to validate different type of expressions passed as tags) - procedure valid_tag_expressions; --%endcontext From e87d39f992da9ff7fdb0cb2b653f6fc0380c6def Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Wed, 29 Mar 2023 15:17:46 -0700 Subject: [PATCH 06/34] Adding validate function, with no calls --- source/core/ut_utils.pkb | 61 ++++++++++++++++++++++++++++++++++++++++ source/core/ut_utils.pks | 7 ++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 162e50f8f..14d0bc148 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -989,6 +989,67 @@ create or replace package body ut_utils is return l_result; end; + function valid_tag_expression(a_tags in varchar2) return number is + t_left_side ut_varchar2_list := ut_varchar2_list('|','&',','); + t_right_side ut_varchar2_list := ut_varchar2_list('!','-'); + l_left_side_expression varchar2(100) := '[|&,]'; + l_left_side_regex varchar(400) := '([^|&,]*)[|&,](.*)'; + l_left_side varchar2(4000); + + l_rigth_side_expression varchar2(100) := '[!-]'; + l_right_side_regex varchar(400) := '([!-])([^!-].*)'; + l_right_side varchar2(4000); + + l_tags varchar2(4000) := a_tags; + l_result number :=1; + begin + --Validate that we have closed up all brackets + if regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)') then + l_result := 0; + end if; + + --Remove brackets as we dont evaluate expression only validate. + l_tags := replace(replace(l_tags,'('),')'); + + --Check if there are any left side operators for first in order from left to right + if regexp_count(l_tags,l_left_side_expression) > 0 then + --Extract left part of operator and remaining of string to right + l_left_side := regexp_replace(l_tags,l_left_side_regex,'\1'); + l_right_side := regexp_replace(l_tags,l_left_side_regex,'\2'); + + --If left side is null that means that we used left side operator without + -- left and right e.g. &test + if l_left_side is null then + l_result := 0; + else + --Extract right side from left side expression if there is any !- + --Remove first negation tag to see if there is double negation + l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); + end if; + + + --check that on right side there is no extra negation + if regexp_count(l_left_side,l_rigth_side_expression) > 0 then + l_result := 0; + end if; + + --Now process right side of string + if l_right_side is not null then + l_result := least(l_result,valid_tag_expression(l_right_side)); + else + l_result := 0; + end if; + else + --We just process single tag. + l_left_side := l_tags; + l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); + if regexp_count(l_left_side,l_rigth_side_expression) > 0 then + l_result := 0; + end if; + end if; + + return l_result; + end; end ut_utils; / diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index f7f7b4f00..06c6956b4 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -476,6 +476,11 @@ create or replace package ut_utils authid definer is * Return value of interval in plain english */ function interval_to_text(a_interval yminterval_unconstrained) return varchar2; - + + /* + * Return number 1 or 0 if the list of tags is valid expression + */ + function valid_tag_expression(a_tags in varchar2) return number; + end ut_utils; / From 97537de9629c19313ccc6157ac95f323a2de6656 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Wed, 29 Mar 2023 15:29:07 -0700 Subject: [PATCH 07/34] Remove a & from text --- source/api/ut_runner.pkb | 7 ++----- source/core/ut_utils.pkb | 2 +- test/ut3_tester/core/test_ut_utils.pkb | 27 ++++++++++++++++++++++++++ test/ut3_tester/core/test_ut_utils.pks | 3 +++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index 94538eb69..a2a69f464 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -95,13 +95,10 @@ 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()); - --TODO:Verify tag tag expression is valid - /* - if regexp_like(l_tags,'[&|]{2,}|[!-]{2,}|[!-][&|]|[^-&|!,]+[-!]|[-!|&][)]') - or (regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)')) then + if ut_utils.valid_tag_expression(l_tags) = 0 then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end if; - */ + 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 diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 14d0bc148..2a47d4d4b 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -1018,7 +1018,7 @@ create or replace package body ut_utils is l_right_side := regexp_replace(l_tags,l_left_side_regex,'\2'); --If left side is null that means that we used left side operator without - -- left and right e.g. &test + -- left and right e.g. |test if l_left_side is null then l_result := 0; else diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 4ed718777..8a497b9f1 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -488,6 +488,33 @@ end; begin ut.expect(l_expected).to_equal(l_actual); end; + + procedure valid_tag_expressions is + begin + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|!tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&!tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1|!tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2')); + + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,-tag2')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1,-tag2')); + + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1|!tag2)|tag3')); + ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1&!tag2)|(tag3&tag4)')); + + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('&!tag2')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!!tag1|!tag2')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2|')); + ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('((!tag1|!tag2)|tag3')); + + end; end test_ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 4d83b5042..0f8545a45 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -157,5 +157,8 @@ create or replace package test_ut_utils is --%endcontext + --%test(Test to validate different type of expressions passed as tags) + procedure valid_tag_expressions; + end test_ut_utils; / From 2a0f99affc9b6b43dfa9e6f00c053c0eada27b14 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Fri, 31 Mar 2023 08:29:14 -0700 Subject: [PATCH 08/34] Extra changes and added tests --- source/core/ut_suite_cache_manager.pkb | 6 +- test/ut3_tester_helper/run_helper.pkb | 94 ++++++++++++++++++++++++++ test/ut3_user/api/test_ut_run.pkb | 33 +++++++++ test/ut3_user/api/test_ut_run.pks | 3 + 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index ff18deabf..ca7945c74 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -273,9 +273,9 @@ create or replace package body ut_suite_cache_manager is '(\(|\)|\||\!|\&)?([^|&!-()]+)(\(|\)|\||\!|\&)?', q'[\1q'<\2>' member of tags\3]'); --replace operands to XPath - l_tags := REGEXP_REPLACE(l_tags, '\|',' or '); - l_tags := REGEXP_REPLACE(l_tags , '\&',' and '); - l_tags := REGEXP_REPLACE(l_tags,q'[(\!)(q'<[^|&!-]+>')( member of tags)]','\2 not \3'); + l_tags := REPLACE(l_tags, '|',' or '); + l_tags := REPLACE(l_tags , '&',' and '); + l_tags := REGEXP_REPLACE(l_tags,q'[(\!)(q'<[^|&!]+?>')( member of tags)]','\2 not \3'); l_tags := '('||l_tags||')'; return l_tags; end; diff --git a/test/ut3_tester_helper/run_helper.pkb b/test/ut3_tester_helper/run_helper.pkb index a132f4943..c73564798 100644 --- a/test/ut3_tester_helper/run_helper.pkb +++ b/test/ut3_tester_helper/run_helper.pkb @@ -343,9 +343,100 @@ create or replace package body run_helper is end; end test_package_3; ]'; + + execute immediate q'[create or replace package test_tag_pkg_1 is + --%suite + --%tags(suite1,release_3_1_13,development,complex,end_to_end) + --%suitepath(suite1) + --%rollback(manual) + + --%test(Test1 from test_tag_pkg_1) + --%tags(test1,development,fast) + procedure test1; + + --%test(Test2 from test_tag_pkg_1) + --%tags(test2,production,slow,patch_3_1_13) + procedure test2; + + end test_tag_pkg_1; + ]'; + + execute immediate q'[create or replace package body test_tag_pkg_1 is + procedure test1 is + begin + dbms_output.put_line('test_tag_pkg_1.test1 executed'); + end; + procedure test2 is + begin + dbms_output.put_line('test_tag_pkg_1.test2 executed'); + end; + end test_tag_pkg_1; + ]'; + + execute immediate q'[create or replace package test_tag_pkg_2 is + --%suite + --%tags(suite2,release_3_1_12,development,simple) + --%suitepath(suite1.suite2) + --%rollback(manual) + + --%test(Test3 from test_tag_pkg_2) + --%tags(test3,development,fast) + procedure test3; + + --%test(Test4 from test_tag_pkg_1) + --%tags(test4,production,slow) + procedure test4; + + end test_tag_pkg_2; + ]'; + + execute immediate q'[create or replace package body test_tag_pkg_2 is + procedure test3 is + begin + dbms_output.put_line('test_tag_pkg_2.test3 executed'); + end; + procedure test4 is + begin + dbms_output.put_line('test_tag_pkg_2.test4 executed'); + end; + end test_tag_pkg_2; + ]'; + + execute immediate q'[create or replace package test_tag_pkg_3 is + --%suite + --%tags(suite3,release_3_1_13,production,simple,end_to_end) + --%suitepath(suite3) + --%rollback(manual) + + --%test(Test5 from test_tag_pkg_3) + --%tags(test5,release_3_1_13,production,patch_3_1_13) + procedure test5; + + --%test(Test6 from test_tag_pkg_3) + --%tags(test6,development,patch_3_1_14) + procedure test6; + + end test_tag_pkg_3; + ]'; + + execute immediate q'[create or replace package body test_tag_pkg_3 is + procedure test5 is + begin + dbms_output.put_line('test_tag_pkg_3.test5 executed'); + end; + procedure test6 is + begin + dbms_output.put_line('test_tag_pkg_3.test6 executed'); + end; + end test_tag_pkg_3; + ]'; + execute immediate q'[grant execute on test_package_1 to public]'; execute immediate q'[grant execute on test_package_2 to public]'; execute immediate q'[grant execute on test_package_3 to public]'; + execute immediate q'[grant execute on test_tag_pkg_1 to public]'; + execute immediate q'[grant execute on test_tag_pkg_2 to public]'; + execute immediate q'[grant execute on test_tag_pkg_3 to public]'; end; procedure drop_ut3_user_tests is @@ -354,6 +445,9 @@ create or replace package body run_helper is execute immediate q'[drop package test_package_1]'; execute immediate q'[drop package test_package_2]'; execute immediate q'[drop package test_package_3]'; + execute immediate q'[drop package test_tag_pkg_1]'; + execute immediate q'[drop package test_tag_pkg_2]'; + execute immediate q'[drop package test_tag_pkg_3]'; end; procedure create_test_suite is diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 6f6e229c8..ee420f310 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -1202,6 +1202,39 @@ procedure tag_exclude_run_fun_pth_lst_lg is ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_package_2.test2%executed%' ); end; + procedure tag_complex_expressions is + l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(a_tags => 'release_3_1_13&(fast|simple)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test6 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'release_3_1_13&(!patch_3_1_13&!patch_3_1_14)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_1.test1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test5%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test6%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'release_3_1_13&(patch_3_1_13&!slow)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(simple&end_to_end)|(development&fast)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_1.test1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_2.test3 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test6 executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + + end; + procedure set_application_info is begin dbms_application_info.set_module( gc_module, gc_action ); diff --git a/test/ut3_user/api/test_ut_run.pks b/test/ut3_user/api/test_ut_run.pks index db02ee8f8..40e870fe4 100644 --- a/test/ut3_user/api/test_ut_run.pks +++ b/test/ut3_user/api/test_ut_run.pks @@ -259,6 +259,9 @@ create or replace package test_ut_run is --%test(Runs tests from given tags and exclude tags - Legacy Notation) procedure tag_include_exclude_run_fun_lg; + --%test(Runs tests suing complex expressions) + procedure tag_complex_expressions; + --%endcontext --%context(ut3_info context) From b30688cae39ea3917c92427528a5649022e76a4b Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Sat, 1 Apr 2023 08:31:13 -0700 Subject: [PATCH 09/34] Address sonar coverage issues. --- source/core/ut_suite_cache_manager.pkb | 14 ++++++++------ source/core/ut_utils.pkb | 14 ++++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index ca7945c74..2ccf20721 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -227,11 +227,10 @@ create or replace package body ut_suite_cache_manager is - = NOT we will perform a replace of that characters into new notation. - || = OR - && = AND - ^ = NOT + | = OR + & = AND + ! = NOT */ - --TODO: How do we prevent when old notation reach 4k an new will be longer? function replace_legacy_tag_notation(a_tags varchar2 ) return varchar2 is l_tags ut_varchar2_list := ut_utils.string_to_table(a_tags,','); @@ -331,8 +330,11 @@ with ) ) t where c.id = t.id and r_num = 1 ]'; - execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; - return l_suite_tags; + execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; + return l_suite_tags; + exception when others then + --If the dynamic SQL fails we will fall gracefully with meaningfull message + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Tag expression, causing error. If expression is correct please report error.'); end; /* diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 751971202..3747b029a 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -990,15 +990,21 @@ create or replace package body ut_utils is return l_result; end; + /* + Purpose of this function is to break down the tag expressions + We can separate operators on left and rigth side. + Left ones are AND and OR as they require an operator on left side to + be valid. Right side is NOT. + In each iteration we breakdown string into parts + + */ function valid_tag_expression(a_tags in varchar2) return number is - t_left_side ut_varchar2_list := ut_varchar2_list('|','&',','); - t_right_side ut_varchar2_list := ut_varchar2_list('!','-'); l_left_side_expression varchar2(100) := '[|&,]'; - l_left_side_regex varchar(400) := '([^|&,]*)[|&,](.*)'; + l_left_side_regex varchar2(400) := '([^|&,]*)[|&,](.*)'; l_left_side varchar2(4000); l_rigth_side_expression varchar2(100) := '[!-]'; - l_right_side_regex varchar(400) := '([!-])([^!-].*)'; + l_right_side_regex varchar2(400) := '([!-])([^!-].*)'; l_right_side varchar2(4000); l_tags varchar2(4000) := a_tags; From 0c41a0f22816b901e0b2adca5441e462feedb88a Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Sat, 1 Apr 2023 09:19:43 -0700 Subject: [PATCH 10/34] Adding tests covering exception of invalid tags --- source/core/ut_suite_cache_manager.pkb | 5 +---- test/ut3_user/api/test_ut_run.pkb | 30 +++++++++++++++++++++++++- test/ut3_user/api/test_ut_run.pks | 4 ++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index 2ccf20721..152b5c0f9 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -331,10 +331,7 @@ with ) t where c.id = t.id and r_num = 1 ]'; execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; - return l_suite_tags; - exception when others then - --If the dynamic SQL fails we will fall gracefully with meaningfull message - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Tag expression, causing error. If expression is correct please report error.'); + return l_suite_tags; end; /* diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index ee420f310..84dd11f9c 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -1223,16 +1223,44 @@ procedure tag_exclude_run_fun_pth_lst_lg is l_results := ut3_tester_helper.run_helper.run(a_tags => 'release_3_1_13&(patch_3_1_13&!slow)'); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); - + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test6%executed%' ); + l_results := ut3_tester_helper.run_helper.run(a_tags => '(simple&end_to_end)|(development&fast)'); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_1.test1 executed%' ); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_2.test3 executed%' ); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test6 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&end_to_end)'); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%test_tag_pkg_3.test5 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test1%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_1.test2%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test6%executed%' ); + + end; + + procedure invalid_tag_expression is + l_results ut3_develop.ut_varchar2_list; + begin + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!!development&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&end_to_end|)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); end; procedure set_application_info is diff --git a/test/ut3_user/api/test_ut_run.pks b/test/ut3_user/api/test_ut_run.pks index 40e870fe4..c57788bff 100644 --- a/test/ut3_user/api/test_ut_run.pks +++ b/test/ut3_user/api/test_ut_run.pks @@ -262,6 +262,10 @@ create or replace package test_ut_run is --%test(Runs tests suing complex expressions) procedure tag_complex_expressions; + --%test(Testing invalid tag expression) + --%throws(-20219) + procedure invalid_tag_expression; + --%endcontext --%context(ut3_info context) From 543685dc94676110099af0b8af1a155918725cd5 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Sat, 1 Apr 2023 11:26:17 -0700 Subject: [PATCH 11/34] Removing that , we will not implement that, there is no benefit at the moment. --- docs/userguide/annotations.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index f3cf73d4d..4017521b5 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1662,8 +1662,6 @@ end ut_sample_test; Tag expressions are boolean expressions with the operators !, & and |. In addition, ( and ) can be used to adjust for operator precedence. -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. - | Operator | Meaning | | -------- | --------| | ! | not | From 0d3cfa166beccb14b19debffa99bb392f85d8bbb Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Sat, 1 Apr 2023 11:30:00 -0700 Subject: [PATCH 12/34] Removing force --- source/core/types/ut_run.tps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/types/ut_run.tps b/source/core/types/ut_run.tps index 8ce80d2a1..1878a2d46 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 force under ut_suite_item ( +create or replace type ut_run under ut_suite_item ( /* utPLSQL - Version 3 Copyright 2016 - 2021 utPLSQL Project From 20e317742e00f5299b2abd2675160d4b2cc38b86 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Sun, 9 Apr 2023 18:47:21 -0700 Subject: [PATCH 13/34] Changing to use Dijkstra algorithm to parse infix notation into postfix ( Reverse Polish Notation). This allows us to more flexibility of using boolean expressions and not limited to flaky regex. --- source/api/ut_runner.pkb | 4 - source/core/types/ut_stack.tpb | 58 ++++++ source/core/types/ut_stack.tps | 26 +++ source/core/ut_suite_cache_manager.pkb | 21 +-- source/core/ut_utils.pkb | 245 ++++++++++++++++++++++++- source/core/ut_utils.pks | 25 +++ source/install.sql | 2 + test/ut3_user/api/test_ut_run.pkb | 2 +- 8 files changed, 363 insertions(+), 20 deletions(-) create mode 100644 source/core/types/ut_stack.tpb create mode 100644 source/core/types/ut_stack.tps diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index a2a69f464..3d4550cf9 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -95,10 +95,6 @@ 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 ut_utils.valid_tag_expression(l_tags) = 0 then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); - end if; - 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 diff --git a/source/core/types/ut_stack.tpb b/source/core/types/ut_stack.tpb new file mode 100644 index 000000000..a7f7ab0c2 --- /dev/null +++ b/source/core/types/ut_stack.tpb @@ -0,0 +1,58 @@ +create or replace type body ut_stack as + /* + utPLSQL - Version 3 + Copyright 2016 - 2021 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. + */ + + constructor function ut_stack( self in out nocopy ut_stack) return self as result is + begin + self.tokens := ut_varchar2_list(); + self.top := 0; + return; + end ut_stack; + + member function peek(self in out nocopy ut_stack) return varchar2 is + l_token varchar2(32767); + begin + if self.tokens.count =0 or self.tokens is null then + l_token := null; + else + l_token := self.tokens(self.tokens.last); + end if; + return l_token; + end; + + member procedure push(self in out nocopy ut_stack, a_token varchar2) is + begin + self.tokens.extend; + self.tokens(self.tokens.last) := a_token; + self.top := self.tokens.count; + end push; + + member procedure pop(self in out nocopy ut_stack,a_cnt in integer default 1) is + begin + self.tokens.trim(a_cnt); + self.top := self.tokens.count; + end pop; + + member function pop(self in out nocopy ut_stack) return varchar2 is + l_token varchar2(32767) := self.tokens(self.tokens.last); + begin + self.pop(); + return l_token; + end; +end; +/ + diff --git a/source/core/types/ut_stack.tps b/source/core/types/ut_stack.tps new file mode 100644 index 000000000..9851b9cc5 --- /dev/null +++ b/source/core/types/ut_stack.tps @@ -0,0 +1,26 @@ +create or replace type ut_stack as object ( + top integer, + tokens ut_varchar2_list, + /* + utPLSQL - Version 3 + Copyright 2016 - 2021 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. + */ + constructor function ut_stack( self in out nocopy ut_stack) return self as result, + member function peek(self in out nocopy ut_stack) return varchar2, + member procedure push(self in out nocopy ut_stack, a_token varchar2), + member procedure pop(self in out nocopy ut_stack,a_cnt in integer default 1), + member function pop(self in out nocopy ut_stack) return varchar2 +) + diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index 152b5c0f9..ae5460ef6 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -234,11 +234,11 @@ create or replace package body ut_suite_cache_manager is function replace_legacy_tag_notation(a_tags varchar2 ) return varchar2 is l_tags ut_varchar2_list := ut_utils.string_to_table(a_tags,','); - l_tags_include varchar2(2000); - l_tags_exclude varchar2(2000); + l_tags_include varchar2(4000); + l_tags_exclude varchar2(4000); l_return_tag varchar2(4000); begin - select listagg( t.column_value,' | ') + select listagg( t.column_value,'|') within group( order by column_value) into l_tags_include from table(l_tags) t @@ -268,15 +268,14 @@ create or replace package body ut_suite_cache_manager is if instr(l_tags,',') > 0 or instr(l_tags,'-') > 0 then l_tags := replace(replace_legacy_tag_notation(l_tags),' '); end if; - l_tags := REGEXP_REPLACE(l_tags, - '(\(|\)|\||\!|\&)?([^|&!-()]+)(\(|\)|\||\!|\&)?', - q'[\1q'<\2>' member of tags\3]'); - --replace operands to XPath + l_tags := ut_utils.convert_postfix_to_infix_where_sql(ut_utils.shunt_logical_expression(l_tags)); l_tags := REPLACE(l_tags, '|',' or '); - l_tags := REPLACE(l_tags , '&',' and '); - l_tags := REGEXP_REPLACE(l_tags,q'[(\!)(q'<[^|&!]+?>')( member of tags)]','\2 not \3'); - l_tags := '('||l_tags||')'; - return l_tags; + l_tags := REPLACE(l_tags ,'&',' and '); + l_tags := REPLACE(l_tags ,'!','not'); + return l_tags; + exception + when ut_utils.ex_invalid_tag_expression then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end; /* diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 3747b029a..c09c15221 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -24,6 +24,16 @@ create or replace package body ut_utils is gc_full_valid_xml_name constant varchar2(50) := '^([_a-zA-Z])([_a-zA-Z0-9\.-])*$'; gc_owner_hash constant integer(11) := dbms_utility.get_hash_value( ut_owner(), 0, power(2,31)-1); + /** + * Constants use in postfix and infix transformations + */ + gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!'); + gc_unary_operator constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator + gc_binary_operator constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator + + type t_precedence_table is table of number index by varchar2(1); + g_precedence t_precedence_table; + function surround_with(a_value varchar2, a_quote_char varchar2) return varchar2 is begin return case when a_quote_char is not null then a_quote_char||a_value||a_quote_char else a_value end; @@ -999,12 +1009,12 @@ create or replace package body ut_utils is */ function valid_tag_expression(a_tags in varchar2) return number is - l_left_side_expression varchar2(100) := '[|&,]'; - l_left_side_regex varchar2(400) := '([^|&,]*)[|&,](.*)'; + l_left_side_expression varchar2(10) := '[|&,]'; + l_left_side_regex varchar2(50) := '([^|&,]*)[|&,](.*)'; l_left_side varchar2(4000); - l_rigth_side_expression varchar2(100) := '[!-]'; - l_right_side_regex varchar2(400) := '([!-])([^!-].*)'; + l_rigth_side_expression varchar2(10) := '[!-]'; + l_right_side_regex varchar2(50) := '([!-])([^!-].*)'; l_right_side varchar2(4000); l_tags varchar2(4000) := a_tags; @@ -1058,5 +1068,232 @@ create or replace package body ut_utils is return l_result; end; + procedure build_tag_expression_filter(a_tags in varchar2,a_expression_tab in out t_expression_tab,a_parent_id varchar2 default null) is + l_left_side_expression varchar2(10) := '[|&,]'; + l_left_side_regex varchar2(50) := '([^|&,]*)([|&,])(.*)'; + l_left_side varchar2(4000); + + l_rigth_side_expression varchar2(10) := '[!-]'; + l_right_side_regex varchar2(50) := '([!-])([^!-].*)'; + l_right_side varchar2(4000); + + l_tags varchar2(4000) := a_tags; + l_result number :=1; + l_expression_rec t_expression_rec; + + begin + if a_expression_tab is null then + a_expression_tab := t_expression_tab(); + end if; + + l_expression_rec.id := sys_guid(); + l_expression_rec.parent_id := a_parent_id; + + if instr(substr(l_tags,1,1),'(',1,1) + instr(substr(l_tags,-1,1),')',-1,1) = 2 then + + if regexp_count(l_tags,l_right_side_regex) = 1 then + l_expression_rec.negated :=1; + l_tags := trim (leading '!' from l_tags); + end if; + + l_expression_rec.left_bracket := 1; + l_tags := trim(leading '(' from l_tags); + l_expression_rec.right_bracket := 1; + l_tags := trim(trailing ')' from l_tags); + end if; + + + --Check if there are any left side operators for first in order from left to right + if regexp_count(l_tags,l_left_side_expression) > 0 then + --Extract left part of operator and remaining of string to right + + --if there are bracketc extract it and record it + + l_left_side := regexp_replace(l_tags,l_left_side_regex,'\1'); + l_expression_rec.log_operator := regexp_replace(l_tags,l_left_side_regex,'\2'); + l_right_side := regexp_replace(l_tags,l_left_side_regex,'\3'); + a_expression_tab.extend; + a_expression_tab(a_expression_tab.last) := l_expression_rec; + + build_tag_expression_filter(l_left_side,a_expression_tab,l_expression_rec.id); + build_tag_expression_filter(l_right_side,a_expression_tab,l_expression_rec.id); + + else + if instr(substr(l_tags,1,1),'(',1,1) + instr(substr(l_tags,-1,1),')',-1,1) = 2 then + + if regexp_count(l_tags,l_right_side_regex) = 1 then + l_expression_rec.negated :=1; + l_tags := trim (leading '!' from l_tags); + end if; + + l_expression_rec.left_bracket := 1; + l_tags := trim(leading '(' from l_tags); + l_expression_rec.right_bracket := 1; + l_tags := trim(trailing ')' from l_tags); + end if; + l_expression_rec.expression := l_tags; + a_expression_tab.extend; + a_expression_tab(a_expression_tab.last) := l_expression_rec; + end if; + + end; + + /* + https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression + */ + function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list is + l_tags varchar2(32767) := a_tags; + l_operator_stack ut_stack := ut_stack(); + l_input_tokens ut_varchar2_list := ut_varchar2_list(); + l_rnp_tokens ut_varchar2_list := ut_varchar2_list(); + l_token varchar2(32767); + l_expect_operand boolean := true; + l_expect_operator boolean := false; + begin + --Tokenize a string into operators and tags + select regexp_substr(l_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts + bulk collect into l_input_tokens + from dual connect by regexp_substr (l_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null; + + --Exuecute modified shunting algorithm + for token in 1..l_input_tokens.count loop + l_token := l_input_tokens(token); + if (l_token member of gc_operators and l_token member of gc_binary_operator) then + if not(l_expect_operator) then + raise ex_invalid_tag_expression; + end if; + while l_operator_stack.top > 0 and (g_precedence(l_operator_stack.peek) > g_precedence(l_token)) loop + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; + end loop; + l_operator_stack.push(l_input_tokens(token)); + l_expect_operand := true; + l_expect_operator:= false; + elsif (l_token member of gc_operators and l_token member of gc_unary_operator) then + if not(l_expect_operand) then + raise ex_invalid_tag_expression; + end if; + l_operator_stack.push(l_input_tokens(token)); + l_expect_operand := true; + l_expect_operator:= false; + elsif l_token = '(' then + if not(l_expect_operand) then + raise ex_invalid_tag_expression; + end if; + l_operator_stack.push(l_input_tokens(token)); + l_expect_operand := true; + l_expect_operator:= false; + elsif l_token = ')' then + if not(l_expect_operator) then + raise ex_invalid_tag_expression; + end if; + while l_operator_stack.peek <> '(' loop + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; + end loop; + l_operator_stack.pop; --Pop the open bracket and discard it + l_expect_operand := false; + l_expect_operator:= true; + else + if not(l_expect_operand) then + raise ex_invalid_tag_expression; + end if; + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) :=l_token; + l_expect_operator := true; + l_expect_operand := false; + end if; + + end loop; + + while l_operator_stack.top > 0 loop + if l_operator_stack.peek in ('(',')') then + raise ex_invalid_tag_expression; + end if; + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop; + end loop; + + return l_rnp_tokens; + end shunt_logical_expression; + + procedure shunt_logical_expression(a_tags in varchar2) is + a_postfix ut_varchar2_list; + begin + a_postfix := ut_utils.shunt_logical_expression(a_tags); + end shunt_logical_expression; + + function convert_postfix_to_infix(a_postfix_exp in ut_varchar2_list) + return varchar2 is + l_infix_stack ut_stack := ut_stack(); + l_right_side varchar2(32767); + l_left_side varchar2(32767); + l_infix_exp varchar2(32767); + begin + for i in 1..a_postfix_exp.count loop + --If token is operand but also single tag + if a_postfix_exp(i) not member of gc_operators then --its operand + l_infix_stack.push(a_postfix_exp(i)); + --If token is unary operator not + elsif a_postfix_exp(i) member of gc_unary_operator then + l_right_side := l_infix_stack.pop; + l_infix_exp := '('||a_postfix_exp(i)||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + --If token is binary operator + elsif a_postfix_exp(i) member of gc_binary_operator then + l_right_side := l_infix_stack.pop; + l_left_side := l_infix_stack.pop; + l_infix_exp := '('||l_left_side||a_postfix_exp(i)||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + else + null; + end if; + end loop; + + return l_infix_stack.pop; + end; + + function convert_postfix_to_infix_where_sql(a_postfix_exp in ut_varchar2_list) + return varchar2 is + l_infix_stack ut_stack := ut_stack(); + l_right_side varchar2(32767); + l_left_side varchar2(32767); + l_infix_exp varchar2(32767); + l_member_token varchar2(20) := ' member of tags'; + begin + for i in 1..a_postfix_exp.count loop + --If token is operand but also single tag + if regexp_count(a_postfix_exp(i),'[!()|&]') = 0 then + l_infix_stack.push(q'[']'||a_postfix_exp(i)||q'[']'||l_member_token); + --If token is operand but containing other expressions + elsif a_postfix_exp(i) not member of gc_operators then + l_infix_stack.push(a_postfix_exp(i)); + --If token is unary operator not + elsif a_postfix_exp(i) member of gc_unary_operator then + l_right_side := l_infix_stack.pop; + l_infix_exp := a_postfix_exp(i)||'('||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + --If token is binary operator + elsif a_postfix_exp(i) member of gc_binary_operator then + l_right_side := l_infix_stack.pop; + l_left_side := l_infix_stack.pop; + l_infix_exp := '('||l_left_side||a_postfix_exp(i)||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + else + null; + end if; + end loop; + + return l_infix_stack.pop; + end; + +begin + --Define operator precedence + g_precedence('!'):=4; + g_precedence('&'):=3; + g_precedence('|'):=2; + g_precedence(')'):=1; + g_precedence('('):=1; + end ut_utils; / diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index e84818cba..2212454f4 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -482,5 +482,30 @@ create or replace package ut_utils authid definer is */ function valid_tag_expression(a_tags in varchar2) return number; + /* + * Return number 1 or 0 if the list of tags is valid expression + */ + procedure build_tag_expression_filter(a_tags in varchar2,a_expression_tab in out t_expression_tab,a_parent_id varchar2 default null); + + /* + * Function that uses Dijkstra algorithm to parse mathematical and logical expression + * and return a list of elements in Reverse Polish Notation ( postfix ) + * As part of execution it will validate expression. + */ + function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list; + + procedure shunt_logical_expression(a_tags in varchar2); + + /* + * Function that converts postfix notation into infix + */ + function convert_postfix_to_infix(a_postfix_exp in ut_varchar2_list) return varchar2; + + /* + * Function that converts postfix notation into infix and creating a string of sql filter + * that checking a tags collections for tags according to posted logic. + */ + function convert_postfix_to_infix_where_sql(a_postfix_exp in ut_varchar2_list) return varchar2; + end ut_utils; / diff --git a/source/install.sql b/source/install.sql index 0873d6a1e..a54977f94 100644 --- a/source/install.sql +++ b/source/install.sql @@ -50,6 +50,8 @@ create or replace context &&ut3_owner._info using &&ut3_owner..ut_session_contex @@install_component.sql 'core/types/ut_key_value_pairs.tps' @@install_component.sql 'core/types/ut_reporter_info.tps' @@install_component.sql 'core/types/ut_reporters_info.tps' +@@install_component.sql 'core/types/ut_stack.tps' +@@install_component.sql 'core/types/ut_stack.tpb' @@install_component.sql 'core/ut_utils.pks' @@install_component.sql 'core/ut_metadata.pks' @@install_component.sql 'core/ut_savepoint_seq.sql' diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 84dd11f9c..7eb377f4b 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -1250,7 +1250,7 @@ procedure tag_exclude_run_fun_pth_lst_lg is procedure invalid_tag_expression is l_results ut3_develop.ut_varchar2_list; begin - l_results := ut3_tester_helper.run_helper.run(a_tags => '(!!development&end_to_end)'); + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development!&end_to_end)'); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); From f51cc993be376e456758b73a21680591fa9130d9 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Sun, 9 Apr 2023 18:52:21 -0700 Subject: [PATCH 14/34] Missing slash at end of type --- source/core/types/ut_stack.tps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/types/ut_stack.tps b/source/core/types/ut_stack.tps index 9851b9cc5..7b8c145c2 100644 --- a/source/core/types/ut_stack.tps +++ b/source/core/types/ut_stack.tps @@ -23,4 +23,4 @@ create or replace type ut_stack as object ( member procedure pop(self in out nocopy ut_stack,a_cnt in integer default 1), member function pop(self in out nocopy ut_stack) return varchar2 ) - +/ \ No newline at end of file From 4b8e2ab124d7819b2a8a442b9fcd31d6d5d5666e Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Sun, 9 Apr 2023 21:26:13 -0700 Subject: [PATCH 15/34] Cleanup. Fix object name length. --- source/core/ut_suite_cache_manager.pkb | 2 +- source/core/ut_utils.pkb | 144 +------------------------ source/core/ut_utils.pks | 12 +-- 3 files changed, 5 insertions(+), 153 deletions(-) diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index ae5460ef6..aea8c667e 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -268,7 +268,7 @@ create or replace package body ut_suite_cache_manager is if instr(l_tags,',') > 0 or instr(l_tags,'-') > 0 then l_tags := replace(replace_legacy_tag_notation(l_tags),' '); end if; - l_tags := ut_utils.convert_postfix_to_infix_where_sql(ut_utils.shunt_logical_expression(l_tags)); + l_tags := ut_utils.convert_postfix_to_infix(ut_utils.shunt_logical_expression(l_tags)); l_tags := REPLACE(l_tags, '|',' or '); l_tags := REPLACE(l_tags ,'&',' and '); l_tags := REPLACE(l_tags ,'!','not'); diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index c09c15221..028a0c756 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -1000,144 +1000,6 @@ create or replace package body ut_utils is return l_result; end; - /* - Purpose of this function is to break down the tag expressions - We can separate operators on left and rigth side. - Left ones are AND and OR as they require an operator on left side to - be valid. Right side is NOT. - In each iteration we breakdown string into parts - - */ - function valid_tag_expression(a_tags in varchar2) return number is - l_left_side_expression varchar2(10) := '[|&,]'; - l_left_side_regex varchar2(50) := '([^|&,]*)[|&,](.*)'; - l_left_side varchar2(4000); - - l_rigth_side_expression varchar2(10) := '[!-]'; - l_right_side_regex varchar2(50) := '([!-])([^!-].*)'; - l_right_side varchar2(4000); - - l_tags varchar2(4000) := a_tags; - l_result number :=1; - begin - --Validate that we have closed up all brackets - if regexp_count(l_tags,'\(') <> regexp_count(l_tags,'\)') then - l_result := 0; - end if; - - --Remove brackets as we dont evaluate expression only validate. - l_tags := replace(replace(l_tags,'('),')'); - - --Check if there are any left side operators for first in order from left to right - if regexp_count(l_tags,l_left_side_expression) > 0 then - --Extract left part of operator and remaining of string to right - l_left_side := regexp_replace(l_tags,l_left_side_regex,'\1'); - l_right_side := regexp_replace(l_tags,l_left_side_regex,'\2'); - - --If left side is null that means that we used left side operator without - -- left and right e.g. |test - if l_left_side is null then - l_result := 0; - else - --Extract right side from left side expression if there is any !- - --Remove first negation tag to see if there is double negation - l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); - end if; - - - --check that on right side there is no extra negation - if regexp_count(l_left_side,l_rigth_side_expression) > 0 then - l_result := 0; - end if; - - --Now process right side of string - if l_right_side is not null then - l_result := least(l_result,valid_tag_expression(l_right_side)); - else - l_result := 0; - end if; - else - --We just process single tag. - l_left_side := l_tags; - l_left_side := regexp_replace(l_left_side,l_right_side_regex,'\2'); - if regexp_count(l_left_side,l_rigth_side_expression) > 0 then - l_result := 0; - end if; - end if; - - return l_result; - end; - - procedure build_tag_expression_filter(a_tags in varchar2,a_expression_tab in out t_expression_tab,a_parent_id varchar2 default null) is - l_left_side_expression varchar2(10) := '[|&,]'; - l_left_side_regex varchar2(50) := '([^|&,]*)([|&,])(.*)'; - l_left_side varchar2(4000); - - l_rigth_side_expression varchar2(10) := '[!-]'; - l_right_side_regex varchar2(50) := '([!-])([^!-].*)'; - l_right_side varchar2(4000); - - l_tags varchar2(4000) := a_tags; - l_result number :=1; - l_expression_rec t_expression_rec; - - begin - if a_expression_tab is null then - a_expression_tab := t_expression_tab(); - end if; - - l_expression_rec.id := sys_guid(); - l_expression_rec.parent_id := a_parent_id; - - if instr(substr(l_tags,1,1),'(',1,1) + instr(substr(l_tags,-1,1),')',-1,1) = 2 then - - if regexp_count(l_tags,l_right_side_regex) = 1 then - l_expression_rec.negated :=1; - l_tags := trim (leading '!' from l_tags); - end if; - - l_expression_rec.left_bracket := 1; - l_tags := trim(leading '(' from l_tags); - l_expression_rec.right_bracket := 1; - l_tags := trim(trailing ')' from l_tags); - end if; - - - --Check if there are any left side operators for first in order from left to right - if regexp_count(l_tags,l_left_side_expression) > 0 then - --Extract left part of operator and remaining of string to right - - --if there are bracketc extract it and record it - - l_left_side := regexp_replace(l_tags,l_left_side_regex,'\1'); - l_expression_rec.log_operator := regexp_replace(l_tags,l_left_side_regex,'\2'); - l_right_side := regexp_replace(l_tags,l_left_side_regex,'\3'); - a_expression_tab.extend; - a_expression_tab(a_expression_tab.last) := l_expression_rec; - - build_tag_expression_filter(l_left_side,a_expression_tab,l_expression_rec.id); - build_tag_expression_filter(l_right_side,a_expression_tab,l_expression_rec.id); - - else - if instr(substr(l_tags,1,1),'(',1,1) + instr(substr(l_tags,-1,1),')',-1,1) = 2 then - - if regexp_count(l_tags,l_right_side_regex) = 1 then - l_expression_rec.negated :=1; - l_tags := trim (leading '!' from l_tags); - end if; - - l_expression_rec.left_bracket := 1; - l_tags := trim(leading '(' from l_tags); - l_expression_rec.right_bracket := 1; - l_tags := trim(trailing ')' from l_tags); - end if; - l_expression_rec.expression := l_tags; - a_expression_tab.extend; - a_expression_tab(a_expression_tab.last) := l_expression_rec; - end if; - - end; - /* https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression */ @@ -1251,9 +1113,9 @@ create or replace package body ut_utils is end loop; return l_infix_stack.pop; - end; + end convert_postfix_to_infix; - function convert_postfix_to_infix_where_sql(a_postfix_exp in ut_varchar2_list) + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) return varchar2 is l_infix_stack ut_stack := ut_stack(); l_right_side varchar2(32767); @@ -1285,7 +1147,7 @@ create or replace package body ut_utils is end loop; return l_infix_stack.pop; - end; + end conv_postfix_to_infix_sql; begin --Define operator precedence diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index 2212454f4..4bc35466d 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -477,16 +477,6 @@ create or replace package ut_utils authid definer is */ function interval_to_text(a_interval yminterval_unconstrained) return varchar2; - /* - * Return number 1 or 0 if the list of tags is valid expression - */ - function valid_tag_expression(a_tags in varchar2) return number; - - /* - * Return number 1 or 0 if the list of tags is valid expression - */ - procedure build_tag_expression_filter(a_tags in varchar2,a_expression_tab in out t_expression_tab,a_parent_id varchar2 default null); - /* * Function that uses Dijkstra algorithm to parse mathematical and logical expression * and return a list of elements in Reverse Polish Notation ( postfix ) @@ -505,7 +495,7 @@ create or replace package ut_utils authid definer is * Function that converts postfix notation into infix and creating a string of sql filter * that checking a tags collections for tags according to posted logic. */ - function convert_postfix_to_infix_where_sql(a_postfix_exp in ut_varchar2_list) return varchar2; + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) return varchar2; end ut_utils; / From 84e8684004ddbcc95318e24903fae9fd046bf12a Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 10 Apr 2023 13:29:07 -0700 Subject: [PATCH 16/34] Update tests after removed function --- test/ut3_tester/core/test_ut_utils.pkb | 27 -------------------------- test/ut3_tester/core/test_ut_utils.pks | 3 --- 2 files changed, 30 deletions(-) diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 8a497b9f1..ec7e4f403 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -489,32 +489,5 @@ end; ut.expect(l_expected).to_equal(l_actual); end; - procedure valid_tag_expressions is - begin - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|!tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1&!tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1|!tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2')); - - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1,-tag2')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('-tag1,-tag2')); - - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1|!tag2)|tag3')); - ut.expect(1).to_equal(ut3_develop.ut_utils.valid_tag_expression('(!tag1&!tag2)|(tag3&tag4)')); - - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('tag1|')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('&!tag2')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!!tag1|!tag2')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('!tag1&!tag2|')); - ut.expect(0).to_equal(ut3_develop.ut_utils.valid_tag_expression('((!tag1|!tag2)|tag3')); - - end; - end test_ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 0f8545a45..4d83b5042 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -157,8 +157,5 @@ create or replace package test_ut_utils is --%endcontext - --%test(Test to validate different type of expressions passed as tags) - procedure valid_tag_expressions; - end test_ut_utils; / From 2e7a766f6169d0ad104448166ff3d0c84280b3e3 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 10 Apr 2023 13:57:54 -0700 Subject: [PATCH 17/34] Tidy up tests --- source/core/ut_suite_cache_manager.pkb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index aea8c667e..a9153c7bd 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -268,7 +268,7 @@ create or replace package body ut_suite_cache_manager is if instr(l_tags,',') > 0 or instr(l_tags,'-') > 0 then l_tags := replace(replace_legacy_tag_notation(l_tags),' '); end if; - l_tags := ut_utils.convert_postfix_to_infix(ut_utils.shunt_logical_expression(l_tags)); + l_tags := ut_utils.conv_postfix_to_infix_sql(ut_utils.shunt_logical_expression(l_tags)); l_tags := REPLACE(l_tags, '|',' or '); l_tags := REPLACE(l_tags ,'&',' and '); l_tags := REPLACE(l_tags ,'!','not'); From cbdf83aee9491bd39e4bc06f74f72745fd5a6ef3 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 10 Apr 2023 14:02:57 -0700 Subject: [PATCH 18/34] Added ut_stack to uninstall --- source/uninstall_objects.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/uninstall_objects.sql b/source/uninstall_objects.sql index 9531736b9..1e21f0f95 100644 --- a/source/uninstall_objects.sql +++ b/source/uninstall_objects.sql @@ -267,6 +267,8 @@ end; drop package ut_utils; +drop type ut_stack force; + drop sequence ut_savepoint_seq; drop type ut_documentation_reporter force; From 436eb5bed919df6b512688511a6d121a71c3a320 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 10 Apr 2023 18:00:07 -0700 Subject: [PATCH 19/34] Addressing test failures and sonar smells --- source/core/ut_utils.pkb | 51 +++++++++++++++----------- test/ut3_tester/core/test_ut_utils.pkb | 35 ++++++++++++++++++ test/ut3_tester/core/test_ut_utils.pks | 6 +++ 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 028a0c756..ac5f25645 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -1011,15 +1011,17 @@ create or replace package body ut_utils is l_token varchar2(32767); l_expect_operand boolean := true; l_expect_operator boolean := false; + l_idx pls_integer; begin --Tokenize a string into operators and tags select regexp_substr(l_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts bulk collect into l_input_tokens from dual connect by regexp_substr (l_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null; + l_idx := l_input_tokens.first; --Exuecute modified shunting algorithm - for token in 1..l_input_tokens.count loop - l_token := l_input_tokens(token); + WHILE (l_idx is not null) loop + l_token := l_input_tokens(l_idx); if (l_token member of gc_operators and l_token member of gc_binary_operator) then if not(l_expect_operator) then raise ex_invalid_tag_expression; @@ -1028,21 +1030,21 @@ create or replace package body ut_utils is l_rnp_tokens.extend; l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; end loop; - l_operator_stack.push(l_input_tokens(token)); + l_operator_stack.push(l_input_tokens(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif (l_token member of gc_operators and l_token member of gc_unary_operator) then if not(l_expect_operand) then raise ex_invalid_tag_expression; end if; - l_operator_stack.push(l_input_tokens(token)); + l_operator_stack.push(l_input_tokens(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = '(' then if not(l_expect_operand) then raise ex_invalid_tag_expression; end if; - l_operator_stack.push(l_input_tokens(token)); + l_operator_stack.push(l_input_tokens(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = ')' then @@ -1066,6 +1068,7 @@ create or replace package body ut_utils is l_expect_operand := false; end if; + l_idx := l_input_tokens.next(l_idx); end loop; while l_operator_stack.top > 0 loop @@ -1091,25 +1094,28 @@ create or replace package body ut_utils is l_right_side varchar2(32767); l_left_side varchar2(32767); l_infix_exp varchar2(32767); + l_idx pls_integer; begin - for i in 1..a_postfix_exp.count loop + l_idx := a_postfix_exp.first; + while (l_idx is not null) loop --If token is operand but also single tag - if a_postfix_exp(i) not member of gc_operators then --its operand - l_infix_stack.push(a_postfix_exp(i)); + if a_postfix_exp(l_idx) not member of gc_operators then --its operand + l_infix_stack.push(a_postfix_exp(l_idx)); --If token is unary operator not - elsif a_postfix_exp(i) member of gc_unary_operator then + elsif a_postfix_exp(l_idx) member of gc_unary_operator then l_right_side := l_infix_stack.pop; - l_infix_exp := '('||a_postfix_exp(i)||l_right_side||')'; + l_infix_exp := '('||a_postfix_exp(l_idx)||l_right_side||')'; l_infix_stack.push(l_infix_exp); --If token is binary operator - elsif a_postfix_exp(i) member of gc_binary_operator then + elsif a_postfix_exp(l_idx) member of gc_binary_operator then l_right_side := l_infix_stack.pop; l_left_side := l_infix_stack.pop; - l_infix_exp := '('||l_left_side||a_postfix_exp(i)||l_right_side||')'; + l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; l_infix_stack.push(l_infix_exp); else null; end if; + l_idx := a_postfix_exp.next(l_idx); end loop; return l_infix_stack.pop; @@ -1122,28 +1128,31 @@ create or replace package body ut_utils is l_left_side varchar2(32767); l_infix_exp varchar2(32767); l_member_token varchar2(20) := ' member of tags'; + l_idx pls_integer; begin - for i in 1..a_postfix_exp.count loop + l_idx := a_postfix_exp.first; + while ( l_idx is not null) loop --If token is operand but also single tag - if regexp_count(a_postfix_exp(i),'[!()|&]') = 0 then - l_infix_stack.push(q'[']'||a_postfix_exp(i)||q'[']'||l_member_token); + if regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then + l_infix_stack.push(q'[']'||a_postfix_exp(l_idx)||q'[']'||l_member_token); --If token is operand but containing other expressions - elsif a_postfix_exp(i) not member of gc_operators then - l_infix_stack.push(a_postfix_exp(i)); + elsif a_postfix_exp(l_idx) not member of gc_operators then + l_infix_stack.push(a_postfix_exp(l_idx)); --If token is unary operator not - elsif a_postfix_exp(i) member of gc_unary_operator then + elsif a_postfix_exp(l_idx) member of gc_unary_operator then l_right_side := l_infix_stack.pop; - l_infix_exp := a_postfix_exp(i)||'('||l_right_side||')'; + l_infix_exp := a_postfix_exp(l_idx)||'('||l_right_side||')'; l_infix_stack.push(l_infix_exp); --If token is binary operator - elsif a_postfix_exp(i) member of gc_binary_operator then + elsif a_postfix_exp(l_idx) member of gc_binary_operator then l_right_side := l_infix_stack.pop; l_left_side := l_infix_stack.pop; - l_infix_exp := '('||l_left_side||a_postfix_exp(i)||l_right_side||')'; + l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; l_infix_stack.push(l_infix_exp); else null; end if; + l_idx := a_postfix_exp.next(l_idx); end loop; return l_infix_stack.pop; diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index ec7e4f403..1f64c621e 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -489,5 +489,40 @@ end; ut.expect(l_expected).to_equal(l_actual); end; + + procedure test_conversion_to_rpn is + l_postfix ut3_develop.ut_varchar2_list; + l_postfix_string varchar2(4000); + begin + l_postfix := ut3_develop.ut_utils.shunt_logical_expression('A'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('A'); + + l_postfix := ut3_develop.ut_utils.shunt_logical_expression('A|B'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('AB|'); + + l_postfix := ut3_develop.ut_utils.shunt_logical_expression('(a|b)|c&d'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('ab|cd&|'); + end; + + procedure test_conversion_from_rpn_to_infix is + l_postfix_rpn ut3_develop.ut_varchar2_list; + l_infix_string varchar2(4000); + begin + l_postfix_rpn := ut3_develop.ut_varchar2_list('A'); + l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); + ut.expect(l_infix_string).to_equal('A'); + + l_postfix_rpn := ut3_develop.ut_varchar2_list('A','B','|'); + l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); + ut.expect(l_infix_string).to_equal('(A|B)'); + + l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','|','c','d','&','|'); + l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); + ut.expect(l_infix_string).to_equal('((a|b)|(c&d))'); + end; + end test_ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 4d83b5042..7bfd9b36e 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -156,6 +156,12 @@ create or replace package test_ut_utils is --%endcontext + + --%test( Test conversion of expression into Reverse Polish Notation) + procedure test_conversion_to_rpn; + + --%test( Test conversion of expression from Reverse Polish Notation into infix) + procedure test_conversion_from_rpn_to_infix; end test_ut_utils; / From 3d77514448e9720ad11589343d171723ad8f1c72 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 10 Apr 2023 18:08:42 -0700 Subject: [PATCH 20/34] Update name --- test/ut3_tester/core/test_ut_utils.pkb | 2 +- test/ut3_tester/core/test_ut_utils.pks | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 1f64c621e..126ada89c 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -507,7 +507,7 @@ end; ut.expect(l_postfix_string).to_equal('ab|cd&|'); end; - procedure test_conversion_from_rpn_to_infix is + procedure test_conv_from_rpn_to_infix is l_postfix_rpn ut3_develop.ut_varchar2_list; l_infix_string varchar2(4000); begin diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 7bfd9b36e..6d11f65d1 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -161,7 +161,7 @@ create or replace package test_ut_utils is procedure test_conversion_to_rpn; --%test( Test conversion of expression from Reverse Polish Notation into infix) - procedure test_conversion_from_rpn_to_infix; + procedure test_conv_from_rpn_to_infix; end test_ut_utils; / From bf6959fc63c4c36ff24f62d28e8a5b1d6b323ce4 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 10 Apr 2023 20:39:25 -0700 Subject: [PATCH 21/34] Update tests and code --- source/core/ut_utils.pkb | 30 +++++++++++--------------- source/core/ut_utils.pks | 7 ++++-- test/ut3_tester/core/test_ut_utils.pkb | 29 +++++++++++++++++++++++-- test/ut3_tester/core/test_ut_utils.pks | 3 +++ test/ut3_user/api/test_ut_run.pkb | 15 ++++++++++++- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index ac5f25645..1c99a3a42 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -1000,24 +1000,30 @@ create or replace package body ut_utils is return l_result; end; + function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list is + l_tags_tokens ut_varchar2_list := ut_varchar2_list(); + begin + --Tokenize a string into operators and tags + select regexp_substr(a_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts + bulk collect into l_tags_tokens + from dual connect by regexp_substr (a_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null; + + return l_tags_tokens; + end; + /* https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression */ function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list is l_tags varchar2(32767) := a_tags; l_operator_stack ut_stack := ut_stack(); - l_input_tokens ut_varchar2_list := ut_varchar2_list(); + l_input_tokens ut_varchar2_list := tokenize_tags_string(a_tags); l_rnp_tokens ut_varchar2_list := ut_varchar2_list(); l_token varchar2(32767); l_expect_operand boolean := true; l_expect_operator boolean := false; l_idx pls_integer; begin - --Tokenize a string into operators and tags - select regexp_substr(l_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts - bulk collect into l_input_tokens - from dual connect by regexp_substr (l_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null; - l_idx := l_input_tokens.first; --Exuecute modified shunting algorithm WHILE (l_idx is not null) loop @@ -1071,7 +1077,7 @@ create or replace package body ut_utils is l_idx := l_input_tokens.next(l_idx); end loop; - while l_operator_stack.top > 0 loop + while l_operator_stack.peek is not null loop if l_operator_stack.peek in ('(',')') then raise ex_invalid_tag_expression; end if; @@ -1082,12 +1088,6 @@ create or replace package body ut_utils is return l_rnp_tokens; end shunt_logical_expression; - procedure shunt_logical_expression(a_tags in varchar2) is - a_postfix ut_varchar2_list; - begin - a_postfix := ut_utils.shunt_logical_expression(a_tags); - end shunt_logical_expression; - function convert_postfix_to_infix(a_postfix_exp in ut_varchar2_list) return varchar2 is l_infix_stack ut_stack := ut_stack(); @@ -1112,8 +1112,6 @@ create or replace package body ut_utils is l_left_side := l_infix_stack.pop; l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; l_infix_stack.push(l_infix_exp); - else - null; end if; l_idx := a_postfix_exp.next(l_idx); end loop; @@ -1149,8 +1147,6 @@ create or replace package body ut_utils is l_left_side := l_infix_stack.pop; l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; l_infix_stack.push(l_infix_exp); - else - null; end if; l_idx := a_postfix_exp.next(l_idx); end loop; diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index 4bc35466d..67b09b2f9 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -476,6 +476,11 @@ create or replace package ut_utils authid definer is * Return value of interval in plain english */ function interval_to_text(a_interval yminterval_unconstrained) return varchar2; + + /* + * Return table of tokens character by character + */ + function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list; /* * Function that uses Dijkstra algorithm to parse mathematical and logical expression @@ -484,8 +489,6 @@ create or replace package ut_utils authid definer is */ function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list; - procedure shunt_logical_expression(a_tags in varchar2); - /* * Function that converts postfix notation into infix */ diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 126ada89c..22733c4ce 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -504,7 +504,11 @@ end; l_postfix := ut3_develop.ut_utils.shunt_logical_expression('(a|b)|c&d'); l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); - ut.expect(l_postfix_string).to_equal('ab|cd&|'); + ut.expect(l_postfix_string).to_equal('ab|cd&|'); + + l_postfix := ut3_develop.ut_utils.shunt_logical_expression('!a|b'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('a!b|'); end; procedure test_conv_from_rpn_to_infix is @@ -521,7 +525,28 @@ end; l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','|','c','d','&','|'); l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); - ut.expect(l_infix_string).to_equal('((a|b)|(c&d))'); + ut.expect(l_infix_string).to_equal('((a|b)|(c&d))'); + + l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','!','|'); + l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); + ut.expect(l_infix_string).to_equal('(a|(!b))'); + end; + + procedure conv_from_rpn_to_sql_filter is + l_postfix_rpn ut3_develop.ut_varchar2_list; + l_infix_string varchar2(4000); + begin + l_postfix_rpn := ut3_develop.ut_varchar2_list('A'); + l_infix_string := ut3_develop.ut_utils.conv_postfix_to_infix_sql(l_postfix_rpn); + ut.expect(l_infix_string).to_equal(q'['A' member of tags]'); + + l_postfix_rpn := ut3_develop.ut_varchar2_list('A','B','|'); + l_infix_string := ut3_develop.ut_utils.conv_postfix_to_infix_sql(l_postfix_rpn); + ut.expect(l_infix_string).to_equal(q'[('A' member of tags|'B' member of tags)]'); + + l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','!','|'); + l_infix_string := ut3_develop.ut_utils.conv_postfix_to_infix_sql(l_postfix_rpn); + ut.expect(l_infix_string).to_equal(q'[('a' member of tags|!('b' member of tags))]'); end; end test_ut_utils; diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 6d11f65d1..1561e1136 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -163,5 +163,8 @@ create or replace package test_ut_utils is --%test( Test conversion of expression from Reverse Polish Notation into infix) procedure test_conv_from_rpn_to_infix; + --%test( Test conversion of expression from Reverse Polish Notation into custom where filter for SQL) + procedure conv_from_rpn_to_sql_filter; + end test_ut_utils; / diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 7eb377f4b..25b50343e 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -1260,7 +1260,20 @@ procedure tag_exclude_run_fun_pth_lst_lg is l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&end_to_end|)'); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); - ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(!development&!!end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(&development&end_to_end)'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + + l_results := ut3_tester_helper.run_helper.run(a_tags => '(development|end_to_end))'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_match('^\s*invalid_tag_expression \[[,\.0-9]+ sec\]\s*$','m'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like('%(FAILED -%'); + end; procedure set_application_info is From d8233ff6587f81c0a0a6430a4f33747bc2723983 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Tue, 11 Apr 2023 20:33:04 -0700 Subject: [PATCH 22/34] fixing typo in docs --- docs/userguide/annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 4017521b5..8e5a5d1ca 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1684,7 +1684,7 @@ Execution of the test is done by using the parameter `a_tags` with tag expressio ```sql linenums="1" -select * from table(ut.run(a_tags => 'fast||!complex')); +select * from table(ut.run(a_tags => 'fast|!complex')); ``` 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. From bd860f602d458575d00010834e5ca28058ec09cb Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Tue, 11 Apr 2023 20:55:34 -0700 Subject: [PATCH 23/34] Removed unused variable --- source/core/ut_utils.pkb | 1 - 1 file changed, 1 deletion(-) diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 1c99a3a42..a8528fa86 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -1015,7 +1015,6 @@ create or replace package body ut_utils is https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression */ function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list is - l_tags varchar2(32767) := a_tags; l_operator_stack ut_stack := ut_stack(); l_input_tokens ut_varchar2_list := tokenize_tags_string(a_tags); l_rnp_tokens ut_varchar2_list := ut_varchar2_list(); From 313d5e9019f1253cc222821c802a11c8cdd7ed1a Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Wed, 12 Apr 2023 18:40:28 -0700 Subject: [PATCH 24/34] Stage 1 Resolving PR comments --- source/api/ut_runner.pkb | 8 ++-- source/core/types/ut_stack.tpb | 2 +- source/core/types/ut_stack.tps | 2 +- source/core/ut_suite_builder.pkb | 2 +- source/core/ut_suite_cache_manager.pkb | 62 +++++++++++++------------- source/core/ut_utils.pkb | 55 +++++------------------ source/core/ut_utils.pks | 5 --- test/ut3_tester/core/test_ut_utils.pkb | 21 --------- test/ut3_tester/core/test_ut_utils.pks | 3 -- 9 files changed, 50 insertions(+), 110 deletions(-) diff --git a/source/api/ut_runner.pkb b/source/api/ut_runner.pkb index 3d4550cf9..3ec2a5393 100644 --- a/source/api/ut_runner.pkb +++ b/source/api/ut_runner.pkb @@ -78,9 +78,7 @@ create or replace package body ut_runner is l_run ut_run; l_coverage_schema_names ut_varchar2_rows; l_paths ut_varchar2_list; - l_random_test_order_seed positive; - l_tags varchar2(4000) := a_tags; - + l_random_test_order_seed positive; begin ut_event_manager.initialize(); if a_reporters is not empty then @@ -135,10 +133,10 @@ create or replace package body ut_runner is 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 + a_run_tags => a_tags ); - ut_suite_manager.configure_execution_by_path(l_paths, l_run.items, l_random_test_order_seed, l_tags); + ut_suite_manager.configure_execution_by_path(l_paths, l_run.items, l_random_test_order_seed, a_tags); if a_force_manual_rollback then l_run.set_rollback_type( a_rollback_type => ut_utils.gc_rollback_manual, a_force => true ); end if; diff --git a/source/core/types/ut_stack.tpb b/source/core/types/ut_stack.tpb index a7f7ab0c2..b5f4e8747 100644 --- a/source/core/types/ut_stack.tpb +++ b/source/core/types/ut_stack.tpb @@ -1,7 +1,7 @@ create or replace type body ut_stack as /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2023 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/types/ut_stack.tps b/source/core/types/ut_stack.tps index 7b8c145c2..23112fdde 100644 --- a/source/core/types/ut_stack.tps +++ b/source/core/types/ut_stack.tps @@ -3,7 +3,7 @@ create or replace type ut_stack as object ( tokens ut_varchar2_list, /* utPLSQL - Version 3 - Copyright 2016 - 2021 utPLSQL Project + Copyright 2016 - 2023 utPLSQL Project Licensed under the Apache License, Version 2.0 (the "License"): you may not use this file except in compliance with the License. diff --git a/source/core/ut_suite_builder.pkb b/source/core/ut_suite_builder.pkb index 4aa2ff915..ebb113370 100644 --- a/source/core/ut_suite_builder.pkb +++ b/source/core/ut_suite_builder.pkb @@ -205,7 +205,7 @@ create or replace package body ut_suite_builder is l_tag_items := ut_utils.trim_list_elements(ut_utils.string_to_table(a_tags_ann_text(l_annotation_pos),',')); if l_tag_items is not empty then for i in 1 .. l_tag_items.count loop - if regexp_like(l_tag_items(i),'^[^-!&|](\S)+$') then + if regexp_like(l_tag_items(i),'^[^-](\S)+$') then l_tags_list.extend(); l_tags_list(l_tags_list.last) := l_tag_items(i); else diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index a9153c7bd..858069c8f 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -238,44 +238,46 @@ create or replace package body ut_suite_cache_manager is l_tags_exclude varchar2(4000); l_return_tag varchar2(4000); begin - select listagg( t.column_value,'|') - within group( order by column_value) - into l_tags_include - from table(l_tags) t - where t.column_value not like '-%'; - - select listagg( replace(t.column_value,'-','!'),' & ') - within group( order by column_value) - into l_tags_exclude - from table(l_tags) t - where t.column_value like '-%'; - - l_return_tag:= - case when l_tags_include is not null then - '('||l_tags_include||')' else null end || - case when l_tags_include is not null and l_tags_exclude is not null then - ' & ' else null end || - case when l_tags_exclude is not null then - '('||l_tags_exclude||')' else null end; + if instr(a_tags,',') > 0 or instr(a_tags,'-') > 0 then + + select '('||listagg( t.column_value,'|') + within group( order by column_value)||')' + into l_tags_include + from table(l_tags) t + where t.column_value not like '-%'; + select '('||listagg( replace(t.column_value,'-','!'),' & ') + within group( order by column_value)||')' + into l_tags_exclude + from table(l_tags) t + where t.column_value like '-%'; + + + l_return_tag:= + case + when l_tags_include <> '()' and l_tags_exclude <> '()' + then l_tags_include || ' & ' || l_tags_exclude + when l_tags_include <> '()' + then l_tags_include + when l_tags_exclude <> '()' + then l_tags_exclude + end; + else + l_return_tag := a_tags; + end if; return l_return_tag; end; function create_where_filter(a_tags varchar2 ) return varchar2 is - l_tags varchar2(4000):= replace(a_tags,' '); + l_tags varchar2(4000); begin - if instr(l_tags,',') > 0 or instr(l_tags,'-') > 0 then - l_tags := replace(replace_legacy_tag_notation(l_tags),' '); - end if; + l_tags := replace(replace_legacy_tag_notation(a_tags),' '); l_tags := ut_utils.conv_postfix_to_infix_sql(ut_utils.shunt_logical_expression(l_tags)); - l_tags := REPLACE(l_tags, '|',' or '); - l_tags := REPLACE(l_tags ,'&',' and '); - l_tags := REPLACE(l_tags ,'!','not'); - return l_tags; - exception - when ut_utils.ex_invalid_tag_expression then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + l_tags := replace(l_tags, '|',' or '); + l_tags := replace(l_tags ,'&',' and '); + l_tags := replace(l_tags ,'!','not'); + return l_tags; end; /* diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index a8528fa86..6bcfbfeff 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -28,8 +28,8 @@ create or replace package body ut_utils is * Constants use in postfix and infix transformations */ gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!'); - gc_unary_operator constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator - gc_binary_operator constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator + gc_unary_operators constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator + gc_binary_operators constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator type t_precedence_table is table of number index by varchar2(1); g_precedence t_precedence_table; @@ -1027,9 +1027,9 @@ create or replace package body ut_utils is --Exuecute modified shunting algorithm WHILE (l_idx is not null) loop l_token := l_input_tokens(l_idx); - if (l_token member of gc_operators and l_token member of gc_binary_operator) then + if (l_token member of gc_operators and l_token member of gc_binary_operators) then if not(l_expect_operator) then - raise ex_invalid_tag_expression; + raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); end if; while l_operator_stack.top > 0 and (g_precedence(l_operator_stack.peek) > g_precedence(l_token)) loop l_rnp_tokens.extend; @@ -1038,23 +1038,23 @@ create or replace package body ut_utils is l_operator_stack.push(l_input_tokens(l_idx)); l_expect_operand := true; l_expect_operator:= false; - elsif (l_token member of gc_operators and l_token member of gc_unary_operator) then + elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then if not(l_expect_operand) then - raise ex_invalid_tag_expression; + raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); end if; l_operator_stack.push(l_input_tokens(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = '(' then if not(l_expect_operand) then - raise ex_invalid_tag_expression; + raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); end if; l_operator_stack.push(l_input_tokens(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = ')' then if not(l_expect_operator) then - raise ex_invalid_tag_expression; + raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); end if; while l_operator_stack.peek <> '(' loop l_rnp_tokens.extend; @@ -1065,7 +1065,7 @@ create or replace package body ut_utils is l_expect_operator:= true; else if not(l_expect_operand) then - raise ex_invalid_tag_expression; + raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); end if; l_rnp_tokens.extend; l_rnp_tokens(l_rnp_tokens.last) :=l_token; @@ -1078,7 +1078,7 @@ create or replace package body ut_utils is while l_operator_stack.peek is not null loop if l_operator_stack.peek in ('(',')') then - raise ex_invalid_tag_expression; + raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); end if; l_rnp_tokens.extend; l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop; @@ -1087,37 +1087,6 @@ create or replace package body ut_utils is return l_rnp_tokens; end shunt_logical_expression; - function convert_postfix_to_infix(a_postfix_exp in ut_varchar2_list) - return varchar2 is - l_infix_stack ut_stack := ut_stack(); - l_right_side varchar2(32767); - l_left_side varchar2(32767); - l_infix_exp varchar2(32767); - l_idx pls_integer; - begin - l_idx := a_postfix_exp.first; - while (l_idx is not null) loop - --If token is operand but also single tag - if a_postfix_exp(l_idx) not member of gc_operators then --its operand - l_infix_stack.push(a_postfix_exp(l_idx)); - --If token is unary operator not - elsif a_postfix_exp(l_idx) member of gc_unary_operator then - l_right_side := l_infix_stack.pop; - l_infix_exp := '('||a_postfix_exp(l_idx)||l_right_side||')'; - l_infix_stack.push(l_infix_exp); - --If token is binary operator - elsif a_postfix_exp(l_idx) member of gc_binary_operator then - l_right_side := l_infix_stack.pop; - l_left_side := l_infix_stack.pop; - l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; - l_infix_stack.push(l_infix_exp); - end if; - l_idx := a_postfix_exp.next(l_idx); - end loop; - - return l_infix_stack.pop; - end convert_postfix_to_infix; - function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) return varchar2 is l_infix_stack ut_stack := ut_stack(); @@ -1136,12 +1105,12 @@ create or replace package body ut_utils is elsif a_postfix_exp(l_idx) not member of gc_operators then l_infix_stack.push(a_postfix_exp(l_idx)); --If token is unary operator not - elsif a_postfix_exp(l_idx) member of gc_unary_operator then + elsif a_postfix_exp(l_idx) member of gc_unary_operators then l_right_side := l_infix_stack.pop; l_infix_exp := a_postfix_exp(l_idx)||'('||l_right_side||')'; l_infix_stack.push(l_infix_exp); --If token is binary operator - elsif a_postfix_exp(l_idx) member of gc_binary_operator then + elsif a_postfix_exp(l_idx) member of gc_binary_operators then l_right_side := l_infix_stack.pop; l_left_side := l_infix_stack.pop; l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index 67b09b2f9..a7f7752fb 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -489,11 +489,6 @@ create or replace package ut_utils authid definer is */ function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list; - /* - * Function that converts postfix notation into infix - */ - function convert_postfix_to_infix(a_postfix_exp in ut_varchar2_list) return varchar2; - /* * Function that converts postfix notation into infix and creating a string of sql filter * that checking a tags collections for tags according to posted logic. diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 22733c4ce..8f3b57fd0 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -511,27 +511,6 @@ end; ut.expect(l_postfix_string).to_equal('a!b|'); end; - procedure test_conv_from_rpn_to_infix is - l_postfix_rpn ut3_develop.ut_varchar2_list; - l_infix_string varchar2(4000); - begin - l_postfix_rpn := ut3_develop.ut_varchar2_list('A'); - l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); - ut.expect(l_infix_string).to_equal('A'); - - l_postfix_rpn := ut3_develop.ut_varchar2_list('A','B','|'); - l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); - ut.expect(l_infix_string).to_equal('(A|B)'); - - l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','|','c','d','&','|'); - l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); - ut.expect(l_infix_string).to_equal('((a|b)|(c&d))'); - - l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','!','|'); - l_infix_string := ut3_develop.ut_utils.convert_postfix_to_infix(l_postfix_rpn); - ut.expect(l_infix_string).to_equal('(a|(!b))'); - end; - procedure conv_from_rpn_to_sql_filter is l_postfix_rpn ut3_develop.ut_varchar2_list; l_infix_string varchar2(4000); diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index 1561e1136..a58082be0 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -160,9 +160,6 @@ create or replace package test_ut_utils is --%test( Test conversion of expression into Reverse Polish Notation) procedure test_conversion_to_rpn; - --%test( Test conversion of expression from Reverse Polish Notation into infix) - procedure test_conv_from_rpn_to_infix; - --%test( Test conversion of expression from Reverse Polish Notation into custom where filter for SQL) procedure conv_from_rpn_to_sql_filter; From 02a071cb43799b8b68aa12cddb8cba757e6654a2 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Thu, 13 Apr 2023 16:47:00 -0700 Subject: [PATCH 25/34] Separate tag logic. --- source/core/ut_suite_cache_manager.pkb | 143 +-------- source/core/ut_suite_cache_manager.pks | 9 +- source/core/ut_suite_manager.pkb | 15 +- source/core/ut_suite_tag_filter.pkb | 288 ++++++++++++++++++ source/core/ut_suite_tag_filter.pks | 48 +++ source/core/ut_utils.pkb | 140 --------- source/core/ut_utils.pks | 18 -- source/install.sql | 2 + test/install_ut3_tester_tests.sql | 2 + .../core/test_ut_suite_tag_filter.pkb | 42 +++ .../core/test_ut_suite_tag_filter.pks | 13 + test/ut3_tester/core/test_ut_utils.pkb | 39 --- test/ut3_tester/core/test_ut_utils.pks | 6 - 13 files changed, 423 insertions(+), 342 deletions(-) create mode 100644 source/core/ut_suite_tag_filter.pkb create mode 100644 source/core/ut_suite_tag_filter.pks create mode 100644 test/ut3_tester/core/test_ut_suite_tag_filter.pkb create mode 100644 test/ut3_tester/core/test_ut_suite_tag_filter.pks diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index 858069c8f..3bb679517 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -220,121 +220,7 @@ create or replace package body ut_suite_cache_manager is and c.name = nvl(upper(sp.procedure_name),c.name)))) where r_num =1; return l_suite_items; end; - - /* - To support a legact tag notation - , = OR - - = NOT - we will perform a replace of that characters into - new notation. - | = OR - & = AND - ! = NOT - */ - function replace_legacy_tag_notation(a_tags varchar2 - ) return varchar2 is - l_tags ut_varchar2_list := ut_utils.string_to_table(a_tags,','); - l_tags_include varchar2(4000); - l_tags_exclude varchar2(4000); - l_return_tag varchar2(4000); - begin - if instr(a_tags,',') > 0 or instr(a_tags,'-') > 0 then - - select '('||listagg( t.column_value,'|') - within group( order by column_value)||')' - into l_tags_include - from table(l_tags) t - where t.column_value not like '-%'; - select '('||listagg( replace(t.column_value,'-','!'),' & ') - within group( order by column_value)||')' - into l_tags_exclude - from table(l_tags) t - where t.column_value like '-%'; - - - l_return_tag:= - case - when l_tags_include <> '()' and l_tags_exclude <> '()' - then l_tags_include || ' & ' || l_tags_exclude - when l_tags_include <> '()' - then l_tags_include - when l_tags_exclude <> '()' - then l_tags_exclude - end; - else - l_return_tag := a_tags; - end if; - return l_return_tag; - end; - - function create_where_filter(a_tags varchar2 - ) return varchar2 is - l_tags varchar2(4000); - begin - l_tags := replace(replace_legacy_tag_notation(a_tags),' '); - l_tags := ut_utils.conv_postfix_to_infix_sql(ut_utils.shunt_logical_expression(l_tags)); - l_tags := replace(l_tags, '|',' or '); - l_tags := replace(l_tags ,'&',' and '); - l_tags := replace(l_tags ,'!','not'); - return l_tags; - end; - - /* - Having a base set of suites we will do a further filter down if there are - any tags defined. - */ - function get_tags_suites ( - a_suite_items ut_suite_cache_rows, - a_tags varchar2 - ) return ut_suite_cache_rows is - l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows(); - l_sql varchar2(32000); - l_tags varchar2(4000):= create_where_filter(a_tags); - begin - l_sql := - q'[ -with - suites_mv as ( - select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags - from table(:suite_items) c - ), - suites_matching_expr as ( - select c.id,c.path as path,c.self_type,c.object_owner,c.tags - from suites_mv c - where c.self_type in ('UT_SUITE','UT_CONTEXT') - and ]'||l_tags||q'[ - ), - tests_matching_expr as ( - select c.id,c.path as path,c.self_type,c.object_owner,c.tags - from suites_mv c where c.self_type in ('UT_TEST') - and ]'||l_tags||q'[ - ), - tests_with_tags_inh_from_suite as ( - select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner - from suites_mv c join suites_matching_expr t - on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner) - ), - tests_with_tags_prom_to_suite as ( - select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner - from suites_mv c join tests_matching_expr t - on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner) - ) - select obj from suites_mv c, - (select id,row_number() over (partition by id order by id) r_num from - (select id - from tests_with_tags_prom_to_suite tst - where ]'||l_tags||q'[ - union all - select id from tests_with_tags_inh_from_suite tst - where ]'||l_tags||q'[ - ) - ) t where c.id = t.id and r_num = 1 ]'; - - execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; - return l_suite_tags; - end; - /* We will sort a suites in hierarchical structure. Sorting from bottom to top so when we consolidate @@ -387,30 +273,29 @@ with end; function get_cached_suite_rows( - a_schema_paths ut_path_items, - a_random_seed positive := null, - a_tags varchar2 := null + a_suites_filtered ut_suite_cache_rows ) return ut_suite_cache_rows is l_results ut_suite_cache_rows := ut_suite_cache_rows(); - l_suite_items ut_suite_cache_rows := ut_suite_cache_rows(); - l_schema_paths ut_path_items; - l_tags varchar2(4000) := a_tags; begin - l_schema_paths := a_schema_paths; - l_suite_items := get_suite_items(a_schema_paths); - if length(l_tags) > 0 then - l_suite_items := get_tags_suites(l_suite_items,l_tags); - end if; - - open c_get_bulk_cache_suite(l_suite_items); + open c_get_bulk_cache_suite(a_suites_filtered); fetch c_get_bulk_cache_suite bulk collect into l_results; close c_get_bulk_cache_suite; return l_results; end; - + function get_cached_suites( + a_schema_paths ut_path_items, + a_random_seed positive := null + ) return ut_suite_cache_rows is + l_suite_items ut_suite_cache_rows := ut_suite_cache_rows(); + l_schema_paths ut_path_items; + begin + l_schema_paths := a_schema_paths; + l_suite_items := get_suite_items(a_schema_paths); + return l_suite_items; + end; function get_schema_parse_time(a_schema_name varchar2) return timestamp result_cache is l_cache_parse_time timestamp; @@ -558,7 +443,7 @@ with a_schema_paths ut_path_items ) return ut_suite_cache_rows is begin - return get_cached_suite_rows( a_schema_paths ); + return get_cached_suite_rows(get_cached_suites( a_schema_paths )); end; function get_suite_items_info( diff --git a/source/core/ut_suite_cache_manager.pks b/source/core/ut_suite_cache_manager.pks index 7f06e95eb..c217abeed 100644 --- a/source/core/ut_suite_cache_manager.pks +++ b/source/core/ut_suite_cache_manager.pks @@ -55,11 +55,14 @@ create or replace package ut_suite_cache_manager authid definer is * Not to be used publicly. Used internally for building suites at runtime. */ function get_cached_suite_rows( + a_suites_filtered ut_suite_cache_rows + ) return ut_suite_cache_rows; + + function get_cached_suites( a_schema_paths ut_path_items, - a_random_seed positive := null, - a_tags varchar2 := null + a_random_seed positive := null ) return ut_suite_cache_rows; - + function get_schema_paths(a_paths in ut_varchar2_list) return ut_path_items; /* diff --git a/source/core/ut_suite_manager.pkb b/source/core/ut_suite_manager.pkb index 0f8629ded..f8a2e002c 100644 --- a/source/core/ut_suite_manager.pkb +++ b/source/core/ut_suite_manager.pkb @@ -355,17 +355,18 @@ create or replace package body ut_suite_manager is a_tags varchar2 := null, a_skip_all_objects boolean := false ) return t_cached_suites_cursor is - l_unfiltered_rows ut_suite_cache_rows; - l_filtered_rows ut_suite_cache_rows; - l_result t_cached_suites_cursor; + l_unfiltered_rows ut_suite_cache_rows; + l_tag_filter_applied ut_suite_cache_rows; + l_filtered_rows ut_suite_cache_rows; + l_result t_cached_suites_cursor; begin - l_unfiltered_rows := ut_suite_cache_manager.get_cached_suite_rows( + l_unfiltered_rows := ut_suite_cache_manager.get_cached_suites( a_schema_paths, - a_random_seed, - a_tags + a_random_seed ); - l_filtered_rows := get_filtered_cursor(l_unfiltered_rows,a_skip_all_objects); + l_tag_filter_applied := ut_suite_tag_filter.apply(l_unfiltered_rows,a_tags); + l_filtered_rows := get_filtered_cursor(ut_suite_cache_manager.get_cached_suite_rows(l_tag_filter_applied),a_skip_all_objects); reconcile_paths_and_suites(a_schema_paths,l_filtered_rows); ut_suite_cache_manager.sort_and_randomize_tests(l_filtered_rows,a_random_seed); diff --git a/source/core/ut_suite_tag_filter.pkb b/source/core/ut_suite_tag_filter.pkb new file mode 100644 index 000000000..f1e055a27 --- /dev/null +++ b/source/core/ut_suite_tag_filter.pkb @@ -0,0 +1,288 @@ +create or replace package body ut_suite_tag_filter is + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 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. + */ + + /** + * Constants use in postfix and infix transformations + */ + gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!'); + gc_unary_operators constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator + gc_binary_operators constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator + + type t_precedence_table is table of number index by varchar2(1); + g_precedence t_precedence_table; + + function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list is + l_tags_tokens ut_varchar2_list := ut_varchar2_list(); + begin + --Tokenize a string into operators and tags + select regexp_substr(a_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts + bulk collect into l_tags_tokens + from dual connect by regexp_substr (a_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null; + + return l_tags_tokens; + end; + + /* + To support a legact tag notation + , = OR + - = NOT + we will perform a replace of that characters into + new notation. + | = OR + & = AND + ! = NOT + */ + function replace_legacy_tag_notation(a_tags varchar2 + ) return varchar2 is + l_tags ut_varchar2_list := ut_utils.string_to_table(a_tags,','); + l_tags_include varchar2(4000); + l_tags_exclude varchar2(4000); + l_return_tag varchar2(4000); + begin + if instr(a_tags,',') > 0 or instr(a_tags,'-') > 0 then + + select '('||listagg( t.column_value,'|') + within group( order by column_value)||')' + into l_tags_include + from table(l_tags) t + where t.column_value not like '-%'; + + select '('||listagg( replace(t.column_value,'-','!'),' & ') + within group( order by column_value)||')' + into l_tags_exclude + from table(l_tags) t + where t.column_value like '-%'; + + + l_return_tag:= + case + when l_tags_include <> '()' and l_tags_exclude <> '()' + then l_tags_include || ' & ' || l_tags_exclude + when l_tags_include <> '()' + then l_tags_include + when l_tags_exclude <> '()' + then l_tags_exclude + end; + else + l_return_tag := a_tags; + end if; + return l_return_tag; + end; + + /* + https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression + */ + function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list is + l_operator_stack ut_stack := ut_stack(); + l_input_tokens ut_varchar2_list := tokenize_tags_string(a_tags); + l_rnp_tokens ut_varchar2_list := ut_varchar2_list(); + l_token varchar2(32767); + l_expect_operand boolean := true; + l_expect_operator boolean := false; + l_idx pls_integer; + begin + l_idx := l_input_tokens.first; + --Exuecute modified shunting algorithm + WHILE (l_idx is not null) loop + l_token := l_input_tokens(l_idx); + if (l_token member of gc_operators and l_token member of gc_binary_operators) then + if not(l_expect_operator) then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + end if; + while l_operator_stack.top > 0 and (g_precedence(l_operator_stack.peek) > g_precedence(l_token)) loop + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; + end loop; + l_operator_stack.push(l_input_tokens(l_idx)); + l_expect_operand := true; + l_expect_operator:= false; + elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then + if not(l_expect_operand) then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + end if; + l_operator_stack.push(l_input_tokens(l_idx)); + l_expect_operand := true; + l_expect_operator:= false; + elsif l_token = '(' then + if not(l_expect_operand) then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + end if; + l_operator_stack.push(l_input_tokens(l_idx)); + l_expect_operand := true; + l_expect_operator:= false; + elsif l_token = ')' then + if not(l_expect_operator) then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + end if; + while l_operator_stack.peek <> '(' loop + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; + end loop; + l_operator_stack.pop; --Pop the open bracket and discard it + l_expect_operand := false; + l_expect_operator:= true; + else + if not(l_expect_operand) then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + end if; + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last) :=l_token; + l_expect_operator := true; + l_expect_operand := false; + end if; + + l_idx := l_input_tokens.next(l_idx); + end loop; + + while l_operator_stack.peek is not null loop + if l_operator_stack.peek in ('(',')') then + raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + end if; + l_rnp_tokens.extend; + l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop; + end loop; + + return l_rnp_tokens; + end shunt_logical_expression; + + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) + return varchar2 is + l_infix_stack ut_stack := ut_stack(); + l_right_side varchar2(32767); + l_left_side varchar2(32767); + l_infix_exp varchar2(32767); + l_member_token varchar2(20) := ' member of tags'; + l_idx pls_integer; + begin + l_idx := a_postfix_exp.first; + while ( l_idx is not null) loop + --If token is operand but also single tag + if regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then + l_infix_stack.push(q'[']'||a_postfix_exp(l_idx)||q'[']'||l_member_token); + --If token is operand but containing other expressions + elsif a_postfix_exp(l_idx) not member of gc_operators then + l_infix_stack.push(a_postfix_exp(l_idx)); + --If token is unary operator not + elsif a_postfix_exp(l_idx) member of gc_unary_operators then + l_right_side := l_infix_stack.pop; + l_infix_exp := a_postfix_exp(l_idx)||'('||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + --If token is binary operator + elsif a_postfix_exp(l_idx) member of gc_binary_operators then + l_right_side := l_infix_stack.pop; + l_left_side := l_infix_stack.pop; + l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; + l_infix_stack.push(l_infix_exp); + end if; + l_idx := a_postfix_exp.next(l_idx); + end loop; + + return l_infix_stack.pop; + end conv_postfix_to_infix_sql; + + function create_where_filter(a_tags varchar2 + ) return varchar2 is + l_tags varchar2(4000); + begin + l_tags := replace(replace_legacy_tag_notation(a_tags),' '); + l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(l_tags)); + l_tags := replace(l_tags, '|',' or '); + l_tags := replace(l_tags ,'&',' and '); + l_tags := replace(l_tags ,'!','not'); + return l_tags; + end; + + + /* + Having a base set of suites we will do a further filter down if there are + any tags defined. + */ + function get_tags_suites ( + a_suite_items ut_suite_cache_rows, + a_tags varchar2 + ) return ut_suite_cache_rows is + l_suite_tags ut_suite_cache_rows := ut_suite_cache_rows(); + l_sql varchar2(32000); + l_tags varchar2(4000):= create_where_filter(a_tags); + begin + l_sql := + q'[ +with + suites_mv as ( + select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags + from table(:suite_items) c + ), + suites_matching_expr as ( + select c.id,c.path as path,c.self_type,c.object_owner,c.tags + from suites_mv c + where c.self_type in ('UT_SUITE','UT_CONTEXT') + and ]'||l_tags||q'[ + ), + tests_matching_expr as ( + select c.id,c.path as path,c.self_type,c.object_owner,c.tags + from suites_mv c where c.self_type in ('UT_TEST') + and ]'||l_tags||q'[ + ), + tests_with_tags_inh_from_suite as ( + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner + from suites_mv c join suites_matching_expr t + on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner) + ), + tests_with_tags_prom_to_suite as ( + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner + from suites_mv c join tests_matching_expr t + on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner) + ) + select obj from suites_mv c, + (select id,row_number() over (partition by id order by id) r_num from + (select id + from tests_with_tags_prom_to_suite tst + where ]'||l_tags||q'[ + union all + select id from tests_with_tags_inh_from_suite tst + where ]'||l_tags||q'[ + ) + ) t where c.id = t.id and r_num = 1 ]'; + + execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; + return l_suite_tags; + end; + + function apply( + a_unfiltered_rows ut_suite_cache_rows, + a_tags varchar2 := null + ) return ut_suite_cache_rows is + l_suite_items ut_suite_cache_rows := a_unfiltered_rows; + begin + if length(a_tags) > 0 then + l_suite_items := get_tags_suites(l_suite_items,a_tags); + end if; + + return l_suite_items; + end; + +begin + --Define operators precedence + g_precedence('!'):=4; + g_precedence('&'):=3; + g_precedence('|'):=2; + g_precedence(')'):=1; + g_precedence('('):=1; + +end ut_suite_tag_filter; +/ diff --git a/source/core/ut_suite_tag_filter.pks b/source/core/ut_suite_tag_filter.pks new file mode 100644 index 000000000..787c72603 --- /dev/null +++ b/source/core/ut_suite_tag_filter.pks @@ -0,0 +1,48 @@ +create or replace package ut_suite_tag_filter authid definer is + /* + utPLSQL - Version 3 + Copyright 2016 - 2023 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. + */ + + /** + * Package that will filter suites by tags + * + */ + + /* + * Return table of tokens character by character + */ + function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list; + + /* + * Function that uses Dijkstra algorithm to parse mathematical and logical expression + * and return a list of elements in Reverse Polish Notation ( postfix ) + * As part of execution it will validate expression. + */ + function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list; + + /* + * Function that converts postfix notation into infix and creating a string of sql filter + * that checking a tags collections for tags according to posted logic. + */ + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) return varchar2; + + function apply( + a_unfiltered_rows ut_suite_cache_rows, + a_tags varchar2 := null + ) return ut_suite_cache_rows; + +end ut_suite_tag_filter; +/ diff --git a/source/core/ut_utils.pkb b/source/core/ut_utils.pkb index 6bcfbfeff..6f972e11c 100644 --- a/source/core/ut_utils.pkb +++ b/source/core/ut_utils.pkb @@ -24,15 +24,6 @@ create or replace package body ut_utils is gc_full_valid_xml_name constant varchar2(50) := '^([_a-zA-Z])([_a-zA-Z0-9\.-])*$'; gc_owner_hash constant integer(11) := dbms_utility.get_hash_value( ut_owner(), 0, power(2,31)-1); - /** - * Constants use in postfix and infix transformations - */ - gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!'); - gc_unary_operators constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator - gc_binary_operators constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator - - type t_precedence_table is table of number index by varchar2(1); - g_precedence t_precedence_table; function surround_with(a_value varchar2, a_quote_char varchar2) return varchar2 is begin @@ -999,136 +990,5 @@ create or replace package body ut_utils is return l_result; end; - - function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list is - l_tags_tokens ut_varchar2_list := ut_varchar2_list(); - begin - --Tokenize a string into operators and tags - select regexp_substr(a_tags,'([^!()|&]+)|([!()|&])', 1, level) as string_parts - bulk collect into l_tags_tokens - from dual connect by regexp_substr (a_tags, '([^!()|&]+)|([!()|&])', 1, level) is not null; - - return l_tags_tokens; - end; - - /* - https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression - */ - function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list is - l_operator_stack ut_stack := ut_stack(); - l_input_tokens ut_varchar2_list := tokenize_tags_string(a_tags); - l_rnp_tokens ut_varchar2_list := ut_varchar2_list(); - l_token varchar2(32767); - l_expect_operand boolean := true; - l_expect_operator boolean := false; - l_idx pls_integer; - begin - l_idx := l_input_tokens.first; - --Exuecute modified shunting algorithm - WHILE (l_idx is not null) loop - l_token := l_input_tokens(l_idx); - if (l_token member of gc_operators and l_token member of gc_binary_operators) then - if not(l_expect_operator) then - raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); - end if; - while l_operator_stack.top > 0 and (g_precedence(l_operator_stack.peek) > g_precedence(l_token)) loop - l_rnp_tokens.extend; - l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; - end loop; - l_operator_stack.push(l_input_tokens(l_idx)); - l_expect_operand := true; - l_expect_operator:= false; - elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then - if not(l_expect_operand) then - raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); - end if; - l_operator_stack.push(l_input_tokens(l_idx)); - l_expect_operand := true; - l_expect_operator:= false; - elsif l_token = '(' then - if not(l_expect_operand) then - raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); - end if; - l_operator_stack.push(l_input_tokens(l_idx)); - l_expect_operand := true; - l_expect_operator:= false; - elsif l_token = ')' then - if not(l_expect_operator) then - raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); - end if; - while l_operator_stack.peek <> '(' loop - l_rnp_tokens.extend; - l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; - end loop; - l_operator_stack.pop; --Pop the open bracket and discard it - l_expect_operand := false; - l_expect_operator:= true; - else - if not(l_expect_operand) then - raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); - end if; - l_rnp_tokens.extend; - l_rnp_tokens(l_rnp_tokens.last) :=l_token; - l_expect_operator := true; - l_expect_operand := false; - end if; - - l_idx := l_input_tokens.next(l_idx); - end loop; - - while l_operator_stack.peek is not null loop - if l_operator_stack.peek in ('(',')') then - raise_application_error(gc_invalid_tag_expression, 'Invalid Tag expression'); - end if; - l_rnp_tokens.extend; - l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop; - end loop; - - return l_rnp_tokens; - end shunt_logical_expression; - - function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) - return varchar2 is - l_infix_stack ut_stack := ut_stack(); - l_right_side varchar2(32767); - l_left_side varchar2(32767); - l_infix_exp varchar2(32767); - l_member_token varchar2(20) := ' member of tags'; - l_idx pls_integer; - begin - l_idx := a_postfix_exp.first; - while ( l_idx is not null) loop - --If token is operand but also single tag - if regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then - l_infix_stack.push(q'[']'||a_postfix_exp(l_idx)||q'[']'||l_member_token); - --If token is operand but containing other expressions - elsif a_postfix_exp(l_idx) not member of gc_operators then - l_infix_stack.push(a_postfix_exp(l_idx)); - --If token is unary operator not - elsif a_postfix_exp(l_idx) member of gc_unary_operators then - l_right_side := l_infix_stack.pop; - l_infix_exp := a_postfix_exp(l_idx)||'('||l_right_side||')'; - l_infix_stack.push(l_infix_exp); - --If token is binary operator - elsif a_postfix_exp(l_idx) member of gc_binary_operators then - l_right_side := l_infix_stack.pop; - l_left_side := l_infix_stack.pop; - l_infix_exp := '('||l_left_side||a_postfix_exp(l_idx)||l_right_side||')'; - l_infix_stack.push(l_infix_exp); - end if; - l_idx := a_postfix_exp.next(l_idx); - end loop; - - return l_infix_stack.pop; - end conv_postfix_to_infix_sql; - -begin - --Define operator precedence - g_precedence('!'):=4; - g_precedence('&'):=3; - g_precedence('|'):=2; - g_precedence(')'):=1; - g_precedence('('):=1; - end ut_utils; / diff --git a/source/core/ut_utils.pks b/source/core/ut_utils.pks index a7f7752fb..b60fc4cdf 100644 --- a/source/core/ut_utils.pks +++ b/source/core/ut_utils.pks @@ -477,23 +477,5 @@ create or replace package ut_utils authid definer is */ function interval_to_text(a_interval yminterval_unconstrained) return varchar2; - /* - * Return table of tokens character by character - */ - function tokenize_tags_string(a_tags in varchar2) return ut_varchar2_list; - - /* - * Function that uses Dijkstra algorithm to parse mathematical and logical expression - * and return a list of elements in Reverse Polish Notation ( postfix ) - * As part of execution it will validate expression. - */ - function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list; - - /* - * Function that converts postfix notation into infix and creating a string of sql filter - * that checking a tags collections for tags according to posted logic. - */ - function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) return varchar2; - end ut_utils; / diff --git a/source/install.sql b/source/install.sql index a54977f94..827213e6c 100644 --- a/source/install.sql +++ b/source/install.sql @@ -155,6 +155,8 @@ create or replace context &&ut3_owner._info using &&ut3_owner..ut_session_contex @@install_component.sql 'core/ut_suite_cache_seq.sql' @@install_component.sql 'core/ut_suite_cache.sql' +@@install_component.sql 'core/ut_suite_tag_filter.pks' +@@install_component.sql 'core/ut_suite_tag_filter.pkb' @@install_component.sql 'core/ut_suite_cache_manager.pks' @@install_component.sql 'core/ut_suite_cache_manager.pkb' @@install_component.sql 'core/ut_suite_builder.pks' diff --git a/test/install_ut3_tester_tests.sql b/test/install_ut3_tester_tests.sql index 8163a39f2..cc96b2b64 100644 --- a/test/install_ut3_tester_tests.sql +++ b/test/install_ut3_tester_tests.sql @@ -17,6 +17,7 @@ alter session set plsql_optimize_level=0; @@ut3_tester/core/annotations/test_annot_disabled_reason.pks @@ut3_tester/core/expectations/test_expectation_processor.pks @@ut3_tester/core/test_ut_utils.pks +@@ut3_tester/core/test_ut_suite_tag_filter.pks @@ut3_tester/core/test_ut_test.pks @@ut3_tester/core/test_ut_suite.pks @@ut3_tester/core/test_ut_executable.pks @@ -35,6 +36,7 @@ alter session set plsql_optimize_level=0; @@ut3_tester/core/annotations/test_annot_disabled_reason.pkb @@ut3_tester/core/expectations/test_expectation_processor.pkb @@ut3_tester/core/test_ut_utils.pkb +@@ut3_tester/core/test_ut_suite_tag_filter.pkb @@ut3_tester/core/test_ut_test.pkb @@ut3_tester/core/test_ut_suite.pkb @@ut3_tester/core/test_ut_executable.pkb diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pkb b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb new file mode 100644 index 000000000..a1d0be1f7 --- /dev/null +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb @@ -0,0 +1,42 @@ +create or replace package body test_ut_suite_tag_filter is + + procedure test_conversion_to_rpn is + l_postfix ut3_develop.ut_varchar2_list; + l_postfix_string varchar2(4000); + begin + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('A'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('A'); + + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('A|B'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('AB|'); + + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('(a|b)|c&d'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('ab|cd&|'); + + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('!a|b'); + l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); + ut.expect(l_postfix_string).to_equal('a!b|'); + end; + + procedure conv_from_rpn_to_sql_filter is + l_postfix_rpn ut3_develop.ut_varchar2_list; + l_infix_string varchar2(4000); + begin + l_postfix_rpn := ut3_develop.ut_varchar2_list('A'); + l_infix_string := ut3_develop.ut_suite_tag_filter.conv_postfix_to_infix_sql(l_postfix_rpn); + ut.expect(l_infix_string).to_equal(q'['A' member of tags]'); + + l_postfix_rpn := ut3_develop.ut_varchar2_list('A','B','|'); + l_infix_string := ut3_develop.ut_suite_tag_filter.conv_postfix_to_infix_sql(l_postfix_rpn); + ut.expect(l_infix_string).to_equal(q'[('A' member of tags|'B' member of tags)]'); + + l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','!','|'); + l_infix_string := ut3_develop.ut_suite_tag_filter.conv_postfix_to_infix_sql(l_postfix_rpn); + ut.expect(l_infix_string).to_equal(q'[('a' member of tags|!('b' member of tags))]'); + end; + +end test_ut_suite_tag_filter; +/ diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pks b/test/ut3_tester/core/test_ut_suite_tag_filter.pks new file mode 100644 index 000000000..093fab856 --- /dev/null +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pks @@ -0,0 +1,13 @@ +create or replace package test_ut_suite_tag_filter is + + --%suite(ut_suite_tag_filter) + --%suitepath(utplsql.ut3_tester.core) + + --%test( Test conversion of expression into Reverse Polish Notation) + procedure test_conversion_to_rpn; + + --%test( Test conversion of expression from Reverse Polish Notation into custom where filter for SQL) + procedure conv_from_rpn_to_sql_filter; + +end test_ut_suite_tag_filter; +/ diff --git a/test/ut3_tester/core/test_ut_utils.pkb b/test/ut3_tester/core/test_ut_utils.pkb index 8f3b57fd0..ec7e4f403 100644 --- a/test/ut3_tester/core/test_ut_utils.pkb +++ b/test/ut3_tester/core/test_ut_utils.pkb @@ -489,44 +489,5 @@ end; ut.expect(l_expected).to_equal(l_actual); end; - - procedure test_conversion_to_rpn is - l_postfix ut3_develop.ut_varchar2_list; - l_postfix_string varchar2(4000); - begin - l_postfix := ut3_develop.ut_utils.shunt_logical_expression('A'); - l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); - ut.expect(l_postfix_string).to_equal('A'); - - l_postfix := ut3_develop.ut_utils.shunt_logical_expression('A|B'); - l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); - ut.expect(l_postfix_string).to_equal('AB|'); - - l_postfix := ut3_develop.ut_utils.shunt_logical_expression('(a|b)|c&d'); - l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); - ut.expect(l_postfix_string).to_equal('ab|cd&|'); - - l_postfix := ut3_develop.ut_utils.shunt_logical_expression('!a|b'); - l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); - ut.expect(l_postfix_string).to_equal('a!b|'); - end; - - procedure conv_from_rpn_to_sql_filter is - l_postfix_rpn ut3_develop.ut_varchar2_list; - l_infix_string varchar2(4000); - begin - l_postfix_rpn := ut3_develop.ut_varchar2_list('A'); - l_infix_string := ut3_develop.ut_utils.conv_postfix_to_infix_sql(l_postfix_rpn); - ut.expect(l_infix_string).to_equal(q'['A' member of tags]'); - - l_postfix_rpn := ut3_develop.ut_varchar2_list('A','B','|'); - l_infix_string := ut3_develop.ut_utils.conv_postfix_to_infix_sql(l_postfix_rpn); - ut.expect(l_infix_string).to_equal(q'[('A' member of tags|'B' member of tags)]'); - - l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','!','|'); - l_infix_string := ut3_develop.ut_utils.conv_postfix_to_infix_sql(l_postfix_rpn); - ut.expect(l_infix_string).to_equal(q'[('a' member of tags|!('b' member of tags))]'); - end; - end test_ut_utils; / diff --git a/test/ut3_tester/core/test_ut_utils.pks b/test/ut3_tester/core/test_ut_utils.pks index a58082be0..114b35e86 100644 --- a/test/ut3_tester/core/test_ut_utils.pks +++ b/test/ut3_tester/core/test_ut_utils.pks @@ -157,11 +157,5 @@ create or replace package test_ut_utils is --%endcontext - --%test( Test conversion of expression into Reverse Polish Notation) - procedure test_conversion_to_rpn; - - --%test( Test conversion of expression from Reverse Polish Notation into custom where filter for SQL) - procedure conv_from_rpn_to_sql_filter; - end test_ut_utils; / From b8b66ee715c6765d5fef1ad92d8c25513bf757c5 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Thu, 13 Apr 2023 17:00:41 -0700 Subject: [PATCH 26/34] Fix uninstall --- source/uninstall_objects.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/uninstall_objects.sql b/source/uninstall_objects.sql index 1e21f0f95..f488b8b8d 100644 --- a/source/uninstall_objects.sql +++ b/source/uninstall_objects.sql @@ -95,6 +95,8 @@ drop package ut_suite_manager; drop package ut_suite_builder; +drop package ut_suite_tag_filter; + drop package ut_suite_cache_manager; drop table ut_suite_cache purge; From 077fdb11316cda8b99b9ccc909a1c9cb3de57267 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Fri, 14 Apr 2023 15:48:56 -0700 Subject: [PATCH 27/34] Various PR fixe --- docs/userguide/annotations.md | 82 +------------------ docs/userguide/running-unit-tests.md | 76 +++++++++++++++-- source/core/ut_suite_tag_filter.pkb | 18 ++-- source/core/ut_suite_tag_filter.pks | 2 +- .../core/test_ut_suite_tag_filter.pkb | 49 ++++++++++- .../core/test_ut_suite_tag_filter.pks | 20 +++++ 6 files changed, 145 insertions(+), 102 deletions(-) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 8e5a5d1ca..552b02b52 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1616,7 +1616,7 @@ or Tags are defined as a comma separated list within the `--%tags` annotation. -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. +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 explicitly at runtime with a negated tag expression. Parent suite tests are not executed, but a suitepath hierarchy is kept. Sample test suite package with tags. @@ -1657,52 +1657,6 @@ create or replace package body ut_sample_test is end ut_sample_test; / ``` - -#### Tag Expressions - -Tag expressions are boolean expressions with the operators !, & and |. In addition, ( and ) can be used to adjust for operator precedence. - -| Operator | Meaning | -| -------- | --------| -| ! | not | -| & | and | -| \| | or | - -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. - - -| Tag Expression | Selection | -| -------- | --------| -| product | all tests for product | -| catalog \| shipping | all tests for catalog plus all tests for shipping | -| catalog & shipping | all tests for the intersection between catalog and shipping | -| product & !end-to-end | all tests for product, but not the end-to-end tests | -| (micro \| integration) & (product \| shipping) | all micro or integration tests for product or shipping | - - -Execution of the test is done by using the parameter `a_tags` with tag expressions - - -```sql linenums="1" -select * from table(ut.run(a_tags => 'fast|!complex')); -``` -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. - -```sql linenums="1" -select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'api')); -``` -The above call will execute all tests from `ut_sample_test` package as the whole suite is tagged with `api` - -```sql linenums="1" -select * from table(ut.run(a_tags => 'complex')); -``` -The above call will execute only the `ut_sample_test.ut_refcursors1` test, as only the test `ut_refcursors1` is tagged with `complex` - -```sql linenums="1" -select * from table(ut.run(a_tags => 'fast')); -``` -The above call will execute both `ut_sample_test.ut_refcursors1` and `ut_sample_test.ut_test` tests, as both tests are tagged with `fast` - #### Tag naming convention Tags must follow the below naming convention: @@ -1715,45 +1669,11 @@ Tags must follow the below naming convention: - vertical bar (|) - exclamation point (!) - tag cannot be null or blank -- tag cannot contain whitespace - tag cannot start with a dash, e.g. `-some-stuff` is **not** a valid tag - 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` - leading and trailing spaces are ignored in tag name, e.g. `--%tags( tag1 , tag2 )` becomes `tag1` and `tag2` tag names -#### Excluding tests/suites by tags - -It is possible to exclude parts of test suites with tags. -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. -Examples (based on above sample test suite) - -```sql linenums="1" -select * from table(ut.run(a_tags => '(api|fast)&!complex')); -``` - -which is equivalent of legacy calling: - -```sql linenums="1" -select * from table(ut.run(a_tags => 'api,fast,-complex')); -``` - -or - -```sql linenums="1" -select * from table(ut.run(a_tags => '(api|fast)&(!complex&!test1)')); -``` - -which is equivalent of legacy calling: - -```sql linenums="1" -select * from table(ut.run(a_tags => 'api,fast,-complex,-test1')); -``` - -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`. -Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. - - - ### Suitepath It is very likely that the application for which you are going to introduce tests consists of many different packages, procedures and functions. diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index 978803b9a..9abc7c964 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -319,23 +319,85 @@ select * from table(ut.run('hr.test_apply_bonus', a_random_test_order_seed => 30 In addition to the path, you can filter the tests to be run by specifying tags. Tags are defined in the test / context / suite with the `--%tags`-annotation ([Read more](annotations.md#tags)). Multiple tags are separated by comma. -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. + + +### Tag Expressions + +Tag expressions are boolean expressions with the operators !, & and |. In addition, ( and ) can be used to adjust for operator precedence. + +| Operator | Meaning | +| -------- | --------| +| ! | not | +| & | and | +| \| | or | + +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. + + +| Tag Expression | Selection | +| -------- | --------| +| product | all tests for product | +| catalog \| shipping | all tests for catalog plus all tests for shipping | +| catalog & shipping | all tests for the intersection between catalog and shipping | +| product & !end-to-end | all tests for product, but not the end-to-end tests | +| (micro \| integration) & (product \| shipping) | all micro or integration tests for product or shipping | + + +Execution of the test is done by using the parameter `a_tags` with tag expressions + ```sql linenums="1" -begin - ut.run('hr.test_apply_bonus', a_tags => 'test1,test2'); -end; +select * from table(ut.run(a_tags => 'fast|!complex')); +``` +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. + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'api')); +``` +The above call will execute all tests from `ut_sample_test` package as the whole suite is tagged with `api` + +```sql linenums="1" +select * from table(ut.run(a_tags => 'complex')); ``` +The above call will execute only the `ut_sample_test.ut_refcursors1` test, as only the test `ut_refcursors1` is tagged with `complex` + ```sql linenums="1" -select * from table(ut.run('hr.test_apply_bonus', a_tags => 'suite1')) +select * from table(ut.run(a_tags => 'fast')); ``` +The above call will execute both `ut_sample_test.ut_refcursors1` and `ut_sample_test.ut_test` tests, as both tests are tagged with `fast` + +### Excluding tests/suites by tags -You can also exclude specific tags by adding a `-` (dash) in front of the tag +It is possible to exclude parts of test suites with tags. +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. +Examples (based on above sample test suite) ```sql linenums="1" -select * from table(ut.run('hr.test_apply_bonus', a_tags => '-suite1')) +select * from table(ut.run(a_tags => '(api|fast)&!complex')); ``` +which is equivalent of legacy calling: + +```sql linenums="1" +select * from table(ut.run(a_tags => 'api,fast,-complex')); +``` + +or + +```sql linenums="1" +select * from table(ut.run(a_tags => '(api|fast)&(!complex&!test1)')); +``` + +which is equivalent of legacy calling: + +```sql linenums="1" +select * from table(ut.run(a_tags => 'api,fast,-complex,-test1')); +``` + +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`. +Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. + + ## Keeping uncommitted data after test-run utPLSQL by default runs tests in autonomous transaction and performs automatic rollback to assure that tests do not impact one-another and do not have impact on the current session in your IDE. diff --git a/source/core/ut_suite_tag_filter.pkb b/source/core/ut_suite_tag_filter.pkb index f1e055a27..d5df54212 100644 --- a/source/core/ut_suite_tag_filter.pkb +++ b/source/core/ut_suite_tag_filter.pkb @@ -87,19 +87,18 @@ create or replace package body ut_suite_tag_filter is /* https://stackoverflow.com/questions/29634992/shunting-yard-validate-expression */ - function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list is + function shunt_logical_expression(a_tags in ut_varchar2_list) return ut_varchar2_list is l_operator_stack ut_stack := ut_stack(); - l_input_tokens ut_varchar2_list := tokenize_tags_string(a_tags); l_rnp_tokens ut_varchar2_list := ut_varchar2_list(); l_token varchar2(32767); l_expect_operand boolean := true; l_expect_operator boolean := false; l_idx pls_integer; begin - l_idx := l_input_tokens.first; + l_idx := a_tags.first; --Exuecute modified shunting algorithm WHILE (l_idx is not null) loop - l_token := l_input_tokens(l_idx); + l_token := a_tags(l_idx); if (l_token member of gc_operators and l_token member of gc_binary_operators) then if not(l_expect_operator) then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); @@ -108,21 +107,21 @@ create or replace package body ut_suite_tag_filter is l_rnp_tokens.extend; l_rnp_tokens(l_rnp_tokens.last) := l_operator_stack.pop; end loop; - l_operator_stack.push(l_input_tokens(l_idx)); + l_operator_stack.push(a_tags(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then if not(l_expect_operand) then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end if; - l_operator_stack.push(l_input_tokens(l_idx)); + l_operator_stack.push(a_tags(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = '(' then if not(l_expect_operand) then raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); end if; - l_operator_stack.push(l_input_tokens(l_idx)); + l_operator_stack.push(a_tags(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = ')' then @@ -146,7 +145,7 @@ create or replace package body ut_suite_tag_filter is l_expect_operand := false; end if; - l_idx := l_input_tokens.next(l_idx); + l_idx := a_tags.next(l_idx); end loop; while l_operator_stack.peek is not null loop @@ -198,9 +197,10 @@ create or replace package body ut_suite_tag_filter is function create_where_filter(a_tags varchar2 ) return varchar2 is l_tags varchar2(4000); + l_tokenized_tags ut_varchar2_list; begin l_tags := replace(replace_legacy_tag_notation(a_tags),' '); - l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(l_tags)); + l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(tokenize_tags_string(l_tags))); l_tags := replace(l_tags, '|',' or '); l_tags := replace(l_tags ,'&',' and '); l_tags := replace(l_tags ,'!','not'); diff --git a/source/core/ut_suite_tag_filter.pks b/source/core/ut_suite_tag_filter.pks index 787c72603..37ba19111 100644 --- a/source/core/ut_suite_tag_filter.pks +++ b/source/core/ut_suite_tag_filter.pks @@ -31,7 +31,7 @@ create or replace package ut_suite_tag_filter authid definer is * and return a list of elements in Reverse Polish Notation ( postfix ) * As part of execution it will validate expression. */ - function shunt_logical_expression(a_tags in varchar2) return ut_varchar2_list; + function shunt_logical_expression(a_tags in ut_varchar2_list) return ut_varchar2_list; /* * Function that converts postfix notation into infix and creating a string of sql filter diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pkb b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb index a1d0be1f7..a396c6d25 100644 --- a/test/ut3_tester/core/test_ut_suite_tag_filter.pkb +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb @@ -3,24 +3,65 @@ create or replace package body test_ut_suite_tag_filter is procedure test_conversion_to_rpn is l_postfix ut3_develop.ut_varchar2_list; l_postfix_string varchar2(4000); + l_input_token ut3_develop.ut_varchar2_list; begin - l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('A'); + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('A'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); ut.expect(l_postfix_string).to_equal('A'); - l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('A|B'); + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('A|B'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); ut.expect(l_postfix_string).to_equal('AB|'); - l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('(a|b)|c&d'); + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('(a|b)|c&d'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); ut.expect(l_postfix_string).to_equal('ab|cd&|'); - l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression('!a|b'); + l_input_token := ut3_develop.ut_suite_tag_filter.tokenize_tags_string('!a|b'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); l_postfix_string := ut3_develop.ut_utils.table_to_clob(l_postfix,''); ut.expect(l_postfix_string).to_equal('a!b|'); end; + procedure test_conversion_opr_by_opr is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list('A','B'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + + procedure test_conversion_oprd_by_opd is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list('|','|'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + + procedure test_conversion_lb_by_oper is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list('(','A','|','B',')','('); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + + procedure test_conversion_rb_by_oprd is + l_postfix ut3_develop.ut_varchar2_list; + l_input_token ut3_develop.ut_varchar2_list; + begin + l_input_token := ut3_develop.ut_varchar2_list(')','A'); + l_postfix := ut3_develop.ut_suite_tag_filter.shunt_logical_expression(l_input_token); + ut.fail('Expected exception but nothing was raised'); + end; + procedure conv_from_rpn_to_sql_filter is l_postfix_rpn ut3_develop.ut_varchar2_list; l_infix_string varchar2(4000); diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pks b/test/ut3_tester/core/test_ut_suite_tag_filter.pks index 093fab856..6d0b9ff69 100644 --- a/test/ut3_tester/core/test_ut_suite_tag_filter.pks +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pks @@ -3,9 +3,29 @@ create or replace package test_ut_suite_tag_filter is --%suite(ut_suite_tag_filter) --%suitepath(utplsql.ut3_tester.core) + --%context( Conversion to Reverse Polish Notation) + --%test( Test conversion of expression into Reverse Polish Notation) procedure test_conversion_to_rpn; + --%test( Operator is followed by operator) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_opr_by_opr; + + --%test( Operand is followed by operand) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_oprd_by_opd; + + --%test( Left Bracket is followed by operator) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_lb_by_oper; + + --%test( Right Bracket is followed by operand) + --%throws(ut3_develop.ut_utils.gc_invalid_tag_expression) + procedure test_conversion_rb_by_oprd; + + --%endcontext + --%test( Test conversion of expression from Reverse Polish Notation into custom where filter for SQL) procedure conv_from_rpn_to_sql_filter; From 01e53646ba0e37b02ca476832061053e5aa5e786 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Fri, 14 Apr 2023 23:21:35 -0700 Subject: [PATCH 28/34] Update tests and code --- source/core/ut_suite_tag_filter.pkb | 36 +++++++++---------- source/core/ut_suite_tag_filter.pks | 9 ++++- .../core/test_ut_suite_tag_filter.pkb | 23 ++++++------ .../core/test_ut_suite_tag_filter.pks | 4 +-- 4 files changed, 38 insertions(+), 34 deletions(-) diff --git a/source/core/ut_suite_tag_filter.pkb b/source/core/ut_suite_tag_filter.pkb index d5df54212..df125091d 100644 --- a/source/core/ut_suite_tag_filter.pkb +++ b/source/core/ut_suite_tag_filter.pkb @@ -22,7 +22,9 @@ create or replace package body ut_suite_tag_filter is gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!'); gc_unary_operators constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator gc_binary_operators constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator - + gc_tags_column_name constant varchar2(250) := 'tags'; + gc_exception_msg constant varchar2(200) := 'Invalid tag expression'; + type t_precedence_table is table of number index by varchar2(1); g_precedence t_precedence_table; @@ -101,7 +103,7 @@ create or replace package body ut_suite_tag_filter is l_token := a_tags(l_idx); if (l_token member of gc_operators and l_token member of gc_binary_operators) then if not(l_expect_operator) then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); end if; while l_operator_stack.top > 0 and (g_precedence(l_operator_stack.peek) > g_precedence(l_token)) loop l_rnp_tokens.extend; @@ -112,21 +114,21 @@ create or replace package body ut_suite_tag_filter is l_expect_operator:= false; elsif (l_token member of gc_operators and l_token member of gc_unary_operators) then if not(l_expect_operand) then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); end if; l_operator_stack.push(a_tags(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = '(' then if not(l_expect_operand) then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); end if; l_operator_stack.push(a_tags(l_idx)); l_expect_operand := true; l_expect_operator:= false; elsif l_token = ')' then if not(l_expect_operator) then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); end if; while l_operator_stack.peek <> '(' loop l_rnp_tokens.extend; @@ -137,7 +139,7 @@ create or replace package body ut_suite_tag_filter is l_expect_operator:= true; else if not(l_expect_operand) then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); end if; l_rnp_tokens.extend; l_rnp_tokens(l_rnp_tokens.last) :=l_token; @@ -150,7 +152,7 @@ create or replace package body ut_suite_tag_filter is while l_operator_stack.peek is not null loop if l_operator_stack.peek in ('(',')') then - raise_application_error(ut_utils.gc_invalid_tag_expression, 'Invalid Tag expression'); + raise_application_error(ut_utils.gc_invalid_tag_expression, gc_exception_msg); end if; l_rnp_tokens.extend; l_rnp_tokens(l_rnp_tokens.last):=l_operator_stack.pop; @@ -159,13 +161,13 @@ create or replace package body ut_suite_tag_filter is return l_rnp_tokens; end shunt_logical_expression; - function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list,a_tags_column_name in varchar2) return varchar2 is l_infix_stack ut_stack := ut_stack(); l_right_side varchar2(32767); l_left_side varchar2(32767); l_infix_exp varchar2(32767); - l_member_token varchar2(20) := ' member of tags'; + l_member_token varchar2(20) := ' member of '||a_tags_column_name; l_idx pls_integer; begin l_idx := a_postfix_exp.first; @@ -173,9 +175,6 @@ create or replace package body ut_suite_tag_filter is --If token is operand but also single tag if regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then l_infix_stack.push(q'[']'||a_postfix_exp(l_idx)||q'[']'||l_member_token); - --If token is operand but containing other expressions - elsif a_postfix_exp(l_idx) not member of gc_operators then - l_infix_stack.push(a_postfix_exp(l_idx)); --If token is unary operator not elsif a_postfix_exp(l_idx) member of gc_unary_operators then l_right_side := l_infix_stack.pop; @@ -197,10 +196,9 @@ create or replace package body ut_suite_tag_filter is function create_where_filter(a_tags varchar2 ) return varchar2 is l_tags varchar2(4000); - l_tokenized_tags ut_varchar2_list; begin l_tags := replace(replace_legacy_tag_notation(a_tags),' '); - l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(tokenize_tags_string(l_tags))); + l_tags := conv_postfix_to_infix_sql(shunt_logical_expression(tokenize_tags_string(l_tags)),gc_tags_column_name); l_tags := replace(l_tags, '|',' or '); l_tags := replace(l_tags ,'&',' and '); l_tags := replace(l_tags ,'!','not'); @@ -224,7 +222,7 @@ create or replace package body ut_suite_tag_filter is q'[ with suites_mv as ( - select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags + select c.id,value(c) as obj,c.path as path,c.self_type,c.object_owner,c.tags as ]'||gc_tags_column_name||q'[ from table(:suite_items) c ), suites_matching_expr as ( @@ -234,17 +232,17 @@ with and ]'||l_tags||q'[ ), tests_matching_expr as ( - select c.id,c.path as path,c.self_type,c.object_owner,c.tags - from suites_mv c where c.self_type in ('UT_TEST') + select c.id,c.path as path,c.self_type,c.object_owner,c.tags as ]'||gc_tags_column_name||q'[ + from suites_mv c where c.self_type in ('UT_TEST') and ]'||l_tags||q'[ ), tests_with_tags_inh_from_suite as ( - select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags as ]'||gc_tags_column_name||q'[ ,c.object_owner from suites_mv c join suites_matching_expr t on (c.path||'.' like t.path || '.%' /*all descendants and self*/ and c.object_owner = t.object_owner) ), tests_with_tags_prom_to_suite as ( - select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags tags,c.object_owner + select c.id,c.self_type,c.path,c.tags multiset union distinct t.tags as ]'||gc_tags_column_name||q'[ ,c.object_owner from suites_mv c join tests_matching_expr t on (t.path||'.' like c.path || '.%' /*all ancestors and self*/ and c.object_owner = t.object_owner) ) diff --git a/source/core/ut_suite_tag_filter.pks b/source/core/ut_suite_tag_filter.pks index 37ba19111..e824ae275 100644 --- a/source/core/ut_suite_tag_filter.pks +++ b/source/core/ut_suite_tag_filter.pks @@ -37,8 +37,15 @@ create or replace package ut_suite_tag_filter authid definer is * Function that converts postfix notation into infix and creating a string of sql filter * that checking a tags collections for tags according to posted logic. */ - function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list) return varchar2; + function conv_postfix_to_infix_sql(a_postfix_exp in ut_varchar2_list,a_tags_column_name in varchar2) + return varchar2; + /* + * Generates a part where clause sql + */ + function create_where_filter(a_tags varchar2) + return varchar2; + function apply( a_unfiltered_rows ut_suite_cache_rows, a_tags varchar2 := null diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pkb b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb index a396c6d25..edfb27cfc 100644 --- a/test/ut3_tester/core/test_ut_suite_tag_filter.pkb +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pkb @@ -62,21 +62,20 @@ create or replace package body test_ut_suite_tag_filter is ut.fail('Expected exception but nothing was raised'); end; - procedure conv_from_rpn_to_sql_filter is - l_postfix_rpn ut3_develop.ut_varchar2_list; - l_infix_string varchar2(4000); + procedure conv_from_tag_to_sql_filter is + l_sql_filter varchar2(4000); begin - l_postfix_rpn := ut3_develop.ut_varchar2_list('A'); - l_infix_string := ut3_develop.ut_suite_tag_filter.conv_postfix_to_infix_sql(l_postfix_rpn); - ut.expect(l_infix_string).to_equal(q'['A' member of tags]'); + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1'); + ut.expect(l_sql_filter).to_equal(q'['test1' member of tags]'); - l_postfix_rpn := ut3_develop.ut_varchar2_list('A','B','|'); - l_infix_string := ut3_develop.ut_suite_tag_filter.conv_postfix_to_infix_sql(l_postfix_rpn); - ut.expect(l_infix_string).to_equal(q'[('A' member of tags|'B' member of tags)]'); + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1|test2'); + ut.expect(l_sql_filter).to_equal(q'[('test1' member of tags or 'test2' member of tags)]'); - l_postfix_rpn := ut3_develop.ut_varchar2_list('a','b','!','|'); - l_infix_string := ut3_develop.ut_suite_tag_filter.conv_postfix_to_infix_sql(l_postfix_rpn); - ut.expect(l_infix_string).to_equal(q'[('a' member of tags|!('b' member of tags))]'); + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1|!test2'); + ut.expect(l_sql_filter).to_equal(q'[('test1' member of tags or not('test2' member of tags))]'); + + l_sql_filter := ut3_develop.ut_suite_tag_filter.create_where_filter('test1&!test2'); + ut.expect(l_sql_filter).to_equal(q'[('test1' member of tags and not('test2' member of tags))]'); end; end test_ut_suite_tag_filter; diff --git a/test/ut3_tester/core/test_ut_suite_tag_filter.pks b/test/ut3_tester/core/test_ut_suite_tag_filter.pks index 6d0b9ff69..0f84b751b 100644 --- a/test/ut3_tester/core/test_ut_suite_tag_filter.pks +++ b/test/ut3_tester/core/test_ut_suite_tag_filter.pks @@ -26,8 +26,8 @@ create or replace package test_ut_suite_tag_filter is --%endcontext - --%test( Test conversion of expression from Reverse Polish Notation into custom where filter for SQL) - procedure conv_from_rpn_to_sql_filter; + --%test( Test conversion of expression from tag into custom where filter for SQL) + procedure conv_from_tag_to_sql_filter; end test_ut_suite_tag_filter; / From dc0b4a60dfa799b360245bc7c39480e11861f564 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Mon, 17 Apr 2023 19:37:03 -0700 Subject: [PATCH 29/34] Addressing changes via PR review. --- docs/userguide/annotations.md | 40 +----------- docs/userguide/running-unit-tests.md | 90 +++++++++++++++++++------- source/core/ut_suite_cache_manager.pkb | 14 +--- source/core/ut_suite_cache_manager.pks | 9 ++- source/core/ut_suite_manager.pkb | 5 +- 5 files changed, 75 insertions(+), 83 deletions(-) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 552b02b52..17fb7d1f2 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1617,46 +1617,8 @@ or Tags are defined as a comma separated list within the `--%tags` annotation. 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 explicitly at runtime with a negated tag expression. -Parent suite tests are not executed, but a suitepath hierarchy is kept. +See [running unit tests](running-unit-tests.md) for more information on using tags to filter test suites that are to be executed. -Sample test suite package with tags. -```sql linenums="1" -create or replace package ut_sample_test is - - --%suite(Sample Test Suite) - --%tags(api) - - --%test(Compare Ref Cursors) - --%tags(complex,fast) - procedure ut_refcursors1; - - --%test(Run equality test) - --%tags(simple,fast) - procedure ut_test; - -end ut_sample_test; -/ - -create or replace package body ut_sample_test is - - procedure ut_refcursors1 is - v_actual sys_refcursor; - v_expected sys_refcursor; - begin - open v_expected for select 1 as test from dual; - open v_actual for select 2 as test from dual; - - ut.expect(v_actual).to_equal(v_expected); - end; - - procedure ut_test is - begin - ut.expect(1).to_equal(0); - end; - -end ut_sample_test; -/ -``` #### Tag naming convention Tags must follow the below naming convention: diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index 9abc7c964..7597ae72a 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -323,7 +323,7 @@ Multiple tags are separated by comma. ### Tag Expressions -Tag expressions are boolean expressions with the operators !, & and |. In addition, ( and ) can be used to adjust for operator precedence. +Tag expressions are boolean expressions created by combining tags with the `!`, `&`, `|` operators. Tag expressions can be grouped using `(` and `)` braces. Grouping tag expressions affects operator precedence. | Operator | Meaning | | -------- | --------| @@ -338,18 +338,76 @@ If you are tagging your tests across multiple dimensions, tag expressions help y | -------- | --------| | product | all tests for product | | catalog \| shipping | all tests for catalog plus all tests for shipping | -| catalog & shipping | all tests for the intersection between catalog and shipping | -| product & !end-to-end | all tests for product, but not the end-to-end tests | +| catalog & shipping | all tests that are tagged with both `catalog` and `shipping` tags | +| product & !end-to-end | all tests tagged `product`, except the tests tagged `end-to-end` | | (micro \| integration) & (product \| shipping) | all micro or integration tests for product or shipping | -Execution of the test is done by using the parameter `a_tags` with tag expressions +Taking the last expression above `(micro | integration) & (product | shipping)` +| --%tags |included in run | +| -------- | --------| +| micro | no | +| integration | no | +| micro | no | +| product | no | +| shipping | no | +| micro | no | +| micro, integration | no | +| product, shipping | no | +| micro, product | yes | +| micro, shipping | yes | +| integration, product | yes | +| integration, shipping | yes | +| integration, micro, shipping | yes | +| integration, micro, product | yes | +| integration, shipping ,product | yes | +| micro, shipping ,product | yes | +| integration, micro, shipping ,product | yes | + + +### Sample execution of test with tags. + +Execution of the test with tag expressions is done using the parameter `a_tags`. +Given a test package `ut_sample_test` defined below ```sql linenums="1" -select * from table(ut.run(a_tags => 'fast|!complex')); +create or replace package ut_sample_test is + + --%suite(Sample Test Suite) + --%tags(api) + + --%test(Compare Ref Cursors) + --%tags(complex,fast) + procedure ut_refcursors1; + + --%test(Run equality test) + --%tags(simple,fast) + procedure ut_test; + +end ut_sample_test; +/ + +create or replace package body ut_sample_test is + + procedure ut_refcursors1 is + v_actual sys_refcursor; + v_expected sys_refcursor; + begin + open v_expected for select 1 as test from dual; + open v_actual for select 2 as test from dual; + + ut.expect(v_actual).to_equal(v_expected); + end; + + procedure ut_test is + begin + ut.expect(1).to_equal(0); + end; + +end ut_sample_test; +/ ``` -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. ```sql linenums="1" select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'api')); @@ -357,9 +415,9 @@ select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'api')); The above call will execute all tests from `ut_sample_test` package as the whole suite is tagged with `api` ```sql linenums="1" -select * from table(ut.run(a_tags => 'complex')); +select * from table(ut.run(a_tags => 'fast&complex')); ``` -The above call will execute only the `ut_sample_test.ut_refcursors1` test, as only the test `ut_refcursors1` is tagged with `complex` +The above call will execute only the `ut_sample_test.ut_refcursors1` test, as only the test `ut_refcursors1` is tagged with `complex` and `fast` ```sql linenums="1" select * from table(ut.run(a_tags => 'fast')); @@ -376,25 +434,13 @@ Examples (based on above sample test suite) select * from table(ut.run(a_tags => '(api|fast)&!complex')); ``` -which is equivalent of legacy calling: - -```sql linenums="1" -select * from table(ut.run(a_tags => 'api,fast,-complex')); -``` - or ```sql linenums="1" -select * from table(ut.run(a_tags => '(api|fast)&(!complex&!test1)')); -``` - -which is equivalent of legacy calling: - -```sql linenums="1" -select * from table(ut.run(a_tags => 'api,fast,-complex,-test1')); +select * from table(ut.run(a_tags => '(api|fast)&!complex&!test1')); ``` -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`. +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` and except those suites/contexts/tests that are marked as `test1`. Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. diff --git a/source/core/ut_suite_cache_manager.pkb b/source/core/ut_suite_cache_manager.pkb index 3bb679517..680624f02 100644 --- a/source/core/ut_suite_cache_manager.pkb +++ b/source/core/ut_suite_cache_manager.pkb @@ -285,18 +285,6 @@ create or replace package body ut_suite_cache_manager is return l_results; end; - function get_cached_suites( - a_schema_paths ut_path_items, - a_random_seed positive := null - ) return ut_suite_cache_rows is - l_suite_items ut_suite_cache_rows := ut_suite_cache_rows(); - l_schema_paths ut_path_items; - begin - l_schema_paths := a_schema_paths; - l_suite_items := get_suite_items(a_schema_paths); - return l_suite_items; - end; - function get_schema_parse_time(a_schema_name varchar2) return timestamp result_cache is l_cache_parse_time timestamp; begin @@ -443,7 +431,7 @@ create or replace package body ut_suite_cache_manager is a_schema_paths ut_path_items ) return ut_suite_cache_rows is begin - return get_cached_suite_rows(get_cached_suites( a_schema_paths )); + return get_cached_suite_rows(get_suite_items(a_schema_paths)); end; function get_suite_items_info( diff --git a/source/core/ut_suite_cache_manager.pks b/source/core/ut_suite_cache_manager.pks index c217abeed..81b1e0136 100644 --- a/source/core/ut_suite_cache_manager.pks +++ b/source/core/ut_suite_cache_manager.pks @@ -58,11 +58,6 @@ create or replace package ut_suite_cache_manager authid definer is a_suites_filtered ut_suite_cache_rows ) return ut_suite_cache_rows; - function get_cached_suites( - a_schema_paths ut_path_items, - a_random_seed positive := null - ) return ut_suite_cache_rows; - function get_schema_paths(a_paths in ut_varchar2_list) return ut_path_items; /* @@ -77,6 +72,10 @@ create or replace package ut_suite_cache_manager authid definer is function get_suite_items_info( a_suite_cache_items ut_suite_cache_rows ) return ut_suite_items_info; + + function get_suite_items ( + a_schema_paths ut_path_items + ) return ut_suite_cache_rows; /* * Retrieves list of cached suite packages. diff --git a/source/core/ut_suite_manager.pkb b/source/core/ut_suite_manager.pkb index f8a2e002c..c46ba66b8 100644 --- a/source/core/ut_suite_manager.pkb +++ b/source/core/ut_suite_manager.pkb @@ -360,10 +360,7 @@ create or replace package body ut_suite_manager is l_filtered_rows ut_suite_cache_rows; l_result t_cached_suites_cursor; begin - l_unfiltered_rows := ut_suite_cache_manager.get_cached_suites( - a_schema_paths, - a_random_seed - ); + l_unfiltered_rows := ut_suite_cache_manager.get_suite_items(a_schema_paths); l_tag_filter_applied := ut_suite_tag_filter.apply(l_unfiltered_rows,a_tags); l_filtered_rows := get_filtered_cursor(ut_suite_cache_manager.get_cached_suite_rows(l_tag_filter_applied),a_skip_all_objects); From ef1c02b3eee5b3a5a71076f1db24a58765cddbda Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Tue, 18 Apr 2023 15:36:11 -0700 Subject: [PATCH 30/34] Update docs --- docs/userguide/running-unit-tests.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index 7597ae72a..fb9a9f602 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -440,7 +440,13 @@ or select * from table(ut.run(a_tags => '(api|fast)&!complex&!test1')); ``` -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` and except those suites/contexts/tests that are marked as `test1`. +which is equivalent of exclusion on whole expression + +```sql linenums="1" +select * from table(ut.run(a_tags => '(api|fast)&!(complex|test1)')); +``` + +The above calls 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` and except those suites/contexts/tests that are marked as `test1`. Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. From 1551ea5583152a0d0b726005906aa6e51f42c38a Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Tue, 25 Apr 2023 09:30:40 -0700 Subject: [PATCH 31/34] Adding any and none --- docs/userguide/annotations.md | 1 + docs/userguide/running-unit-tests.md | 75 ++++++++++++ source/core/ut_suite_tag_filter.pkb | 15 ++- test/ut3_tester_helper/run_helper.pkb | 160 +++++++++++++++++++++++++- test/ut3_user/api/test_ut_run.pkb | 25 ++++ 5 files changed, 271 insertions(+), 5 deletions(-) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 17fb7d1f2..4ec6de59d 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1634,6 +1634,7 @@ Tags must follow the below naming convention: - tag cannot start with a dash, e.g. `-some-stuff` is **not** a valid tag - 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` - leading and trailing spaces are ignored in tag name, e.g. `--%tags( tag1 , tag2 )` becomes `tag1` and `tag2` tag names +- tag cannot be one of two reserved words : `none` and `any`, any tags with that will not be considered. ### Suitepath diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index fb9a9f602..b3a974d53 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -325,6 +325,8 @@ Multiple tags are separated by comma. Tag expressions are boolean expressions created by combining tags with the `!`, `&`, `|` operators. Tag expressions can be grouped using `(` and `)` braces. Grouping tag expressions affects operator precedence. +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. When using `none` be aware that if the suite is tagged it will exclude any tests and children belonging to that suite. + | Operator | Meaning | | -------- | --------| | ! | not | @@ -450,6 +452,79 @@ The above calls will execute all suites/contexts/tests that are marked with any Given the above example package `ut_sample_test`, only `ut_sample_test.ut_test` will be executed. +### Sample execution with `any` and `none` + +Given a sample test package: + +```sql linenums="1" +create or replace package ut_sample_test is + + --%suite(Sample Test Suite) + + --%test(Compare Ref Cursors) + --%tags(complex,fast) + procedure ut_refcursors1; + + --%test(Run equality test) + --%tags(simple,fast) + procedure ut_test; + + --%test(Run equality test no tag) + procedure ut_test_no_tag; + +end ut_sample_test; +/ + +create or replace package body ut_sample_test is + + procedure ut_refcursors1 is + v_actual sys_refcursor; + v_expected sys_refcursor; + begin + open v_expected for select 1 as test from dual; + open v_actual for select 2 as test from dual; + + ut.expect(v_actual).to_equal(v_expected); + end; + + procedure ut_test is + begin + ut.expect(1).to_equal(0); + end; + + procedure ut_test_no_tag is + begin + ut.expect(1).to_equal(0); + end; + +end ut_sample_test; +/ +``` + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'none')); +``` + +The above call will execute tests `ut_test_no_tag` + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'any')); +``` + +The above call will execute tests `ut_test` and `ut_refcursors1` + +```sql linenums="1" +select * from table(ut.run(a_path => 'ut_sample_test',a_tags => 'none|simple')); +``` + +The above call will execute tests `ut_test_no_tag` and `ut_test` + +```sql linenums="1" +select * from table(ut.run(a_tags => 'none|!simple')); +``` + +The above call will execute tests `ut_test_no_tag` and `ut_refcursors1` + ## Keeping uncommitted data after test-run utPLSQL by default runs tests in autonomous transaction and performs automatic rollback to assure that tests do not impact one-another and do not have impact on the current session in your IDE. diff --git a/source/core/ut_suite_tag_filter.pkb b/source/core/ut_suite_tag_filter.pkb index df125091d..2d9436301 100644 --- a/source/core/ut_suite_tag_filter.pkb +++ b/source/core/ut_suite_tag_filter.pkb @@ -22,9 +22,10 @@ create or replace package body ut_suite_tag_filter is gc_operators constant ut_varchar2_list := ut_varchar2_list('|','&','!'); gc_unary_operators constant ut_varchar2_list := ut_varchar2_list('!'); -- right side associative operator gc_binary_operators constant ut_varchar2_list := ut_varchar2_list('|','&'); -- left side associative operator + gc_reserved_tag_words constant ut_varchar2_list := ut_varchar2_list('none','any'); gc_tags_column_name constant varchar2(250) := 'tags'; gc_exception_msg constant varchar2(200) := 'Invalid tag expression'; - + type t_precedence_table is table of number index by varchar2(1); g_precedence t_precedence_table; @@ -172,8 +173,17 @@ create or replace package body ut_suite_tag_filter is begin l_idx := a_postfix_exp.first; while ( l_idx is not null) loop + --If the token we got is a none or any keyword + if a_postfix_exp(l_idx) member of gc_reserved_tag_words then + + l_infix_stack.push( + case + when a_postfix_exp(l_idx) = 'none' then '('||a_tags_column_name||' is empty or '||a_tags_column_name||' is null)' + else a_tags_column_name||' is not empty' + end + ); --If token is operand but also single tag - if regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then + elsif regexp_count(a_postfix_exp(l_idx),'[!()|&]') = 0 then l_infix_stack.push(q'[']'||a_postfix_exp(l_idx)||q'[']'||l_member_token); --If token is unary operator not elsif a_postfix_exp(l_idx) member of gc_unary_operators then @@ -256,7 +266,6 @@ with where ]'||l_tags||q'[ ) ) t where c.id = t.id and r_num = 1 ]'; - execute immediate l_sql bulk collect into l_suite_tags using a_suite_items; return l_suite_tags; end; diff --git a/test/ut3_tester_helper/run_helper.pkb b/test/ut3_tester_helper/run_helper.pkb index c73564798..6289b1642 100644 --- a/test/ut3_tester_helper/run_helper.pkb +++ b/test/ut3_tester_helper/run_helper.pkb @@ -431,12 +431,162 @@ create or replace package body run_helper is end test_tag_pkg_3; ]'; + execute immediate q'[create or replace package suite1_level1_pkg is + + --%suite(suite1_level1) + --%suitepath(any_none) + --%rollback(manual) + + --%test(Test 1 from Suite1 on level 1) + --%tags(suite1,level1,test1,test1_level1) + procedure test1_level1; + + --%test(Test 2 from Suite1 on level 1) + procedure test2_level1; + + end suite1_level1_pkg; + ]'; + + execute immediate q'[create or replace package body suite1_level1_pkg is + procedure test1_level1 is + begin + dbms_output.put_line('suite1_level1_pkg.test1_level1 executed'); + end; + procedure test2_level1 is + begin + dbms_output.put_line('suite1_level1_pkg.test2_level1 executed'); + end; + end suite1_level1_pkg; + ]'; + + execute immediate q'[create or replace package suite1_1_level2_pkg is + + --%suite(suite1_1_level2) + --%suitepath(any_none.suite1_level1) + --%rollback(manual) + + --%test(Test 1 from Suite1_2 on level 2) + --%tags(level2,test1,test1_level2) + procedure suite1_1_test1_level2; + + --%test(Test 2 from Suite1_2 on level 2) + procedure suite1_1_test2_level2; + + end suite1_1_level2_pkg; + ]'; + + execute immediate q'[create or replace package body suite1_1_level2_pkg is + procedure suite1_1_test1_level2 is + begin + dbms_output.put_line('suite1_1_level2_pkg.suite1_1_test1_level2 executed'); + end; + procedure suite1_1_test2_level2 is + begin + dbms_output.put_line('suite1_1_level2_pkg.suite1_1_test2_level2 executed'); + end; + end suite1_1_level2_pkg; + ]'; + + execute immediate q'[create or replace package suite1_2_level2_pkg is + + --%suite(suite1_2_level2) + --%tags(level2,suite1_2,suites) + --%suitepath(any_none.suite1_level1) + --%rollback(manual) + + --%test(Test 1 from Suite1_2 on level 2) + procedure suite1_2_test1_level2; + + --%test(Test 2 from Suite1_2 on level 2) + --%tags(level2,test2,test2_level2) + procedure suite1_2_test2_level1; + + end suite1_2_level2_pkg; + ]'; + + execute immediate q'[create or replace package body suite1_2_level2_pkg is + procedure suite1_2_test1_level2 is + begin + dbms_output.put_line('suite1_2_level2_pkg.suite1_2_test1_level2 executed'); + end; + procedure suite1_2_test2_level1 is + begin + dbms_output.put_line('suite1_2_level2_pkg.suite1_2_test2_level1 executed'); + end; + end suite1_2_level2_pkg; + ]'; + + execute immediate q'[create or replace package suite2_level1_pkg is + + --%suite(suite2_level1) + --%tags(level1,suite2,suites) + --%suitepath(any_none) + --%rollback(manual) + + --%test(Test 1 from Suite1 on level 1) + --%tags(suite2,level1,test1,test1_level1) + procedure test1_level1; + + --%test(Test 2 from Suite1 on level 1) + procedure test2_level1; + + end suite2_level1_pkg; + ]'; + + execute immediate q'[create or replace package body suite2_level1_pkg is + procedure test1_level1 is + begin + dbms_output.put_line('suite2_level1_pkg.test1_level1 executed'); + end; + procedure test2_level1 is + begin + dbms_output.put_line('suite2_level1_pkg.test2_level1 executed'); + end; + end suite2_level1_pkg; + ]'; + + execute immediate q'[create or replace package suite2_2_level2_pkg is + + --%suite(suite2_2_level2) + --%tags(level2,suite2_2,suites) + --%suitepath(any_none.suite2_level1) + --%rollback(manual) + + --%test(Test 1 from Suite2_2 on level 2) + procedure suite2_2_test1_level2; + + --%test(Test 2 from Suite2_2 on level 2) + --%tags(level2,test2,test2_level2) + procedure suite2_2_test2_level2; + + end suite2_2_level2_pkg; + ]'; + + execute immediate q'[create or replace package body suite2_2_level2_pkg is + procedure suite2_2_test1_level2 is + begin + dbms_output.put_line('suite2_2_level2_pkg.suite2_2_test1_level2 executed'); + end; + procedure suite2_2_test2_level2 is + begin + dbms_output.put_line('suite2_2_level2_pkg.suite2_2_test2_level2 executed'); + end; + end suite2_2_level2_pkg; + ]'; + + execute immediate q'[grant execute on test_package_1 to public]'; execute immediate q'[grant execute on test_package_2 to public]'; execute immediate q'[grant execute on test_package_3 to public]'; execute immediate q'[grant execute on test_tag_pkg_1 to public]'; execute immediate q'[grant execute on test_tag_pkg_2 to public]'; - execute immediate q'[grant execute on test_tag_pkg_3 to public]'; + execute immediate q'[grant execute on test_tag_pkg_3 to public]'; + + execute immediate q'[grant execute on suite1_level1_pkg to public]'; + execute immediate q'[grant execute on suite1_1_level2_pkg to public]'; + execute immediate q'[grant execute on suite1_2_level2_pkg to public]'; + execute immediate q'[grant execute on suite2_level1_pkg to public]'; + execute immediate q'[grant execute on suite2_2_level2_pkg to public]'; end; procedure drop_ut3_user_tests is @@ -447,7 +597,13 @@ create or replace package body run_helper is execute immediate q'[drop package test_package_3]'; execute immediate q'[drop package test_tag_pkg_1]'; execute immediate q'[drop package test_tag_pkg_2]'; - execute immediate q'[drop package test_tag_pkg_3]'; + execute immediate q'[drop package test_tag_pkg_3]'; + + execute immediate q'[drop package suite2_2_level2_pkg]'; + execute immediate q'[drop package suite2_level1_pkg]'; + execute immediate q'[drop package suite1_2_level2_pkg]'; + execute immediate q'[drop package suite1_level1_pkg]'; + execute immediate q'[drop package suite1_1_level2_pkg]'; end; procedure create_test_suite is diff --git a/test/ut3_user/api/test_ut_run.pkb b/test/ut3_user/api/test_ut_run.pkb index 25b50343e..e80235744 100644 --- a/test/ut3_user/api/test_ut_run.pkb +++ b/test/ut3_user/api/test_ut_run.pkb @@ -1244,6 +1244,31 @@ procedure tag_exclude_run_fun_pth_lst_lg is ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test3%executed%' ); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_2.test4%executed%' ); ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%test_package_3.test6%executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'any'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_2_level2_pkg.suite2_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite2_2_level2_pkg.suite2_2_test2_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_2_level2_pkg.suite1_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_2_level2_pkg.suite1_2_test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_1_level2_pkg.suite1_1_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_1_level2_pkg.suite1_1_test2_level2 executed%' ); + + l_results := ut3_tester_helper.run_helper.run(a_tags => 'none'); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).to_be_like( '%suite1_1_level2_pkg.suite1_1_test2_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_level1_pkg.test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_level1_pkg.test1_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_2_level2_pkg.suite2_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite2_2_level2_pkg.suite2_2_test2_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_2_level2_pkg.suite1_2_test1_level2 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_2_level2_pkg.suite1_2_test2_level1 executed%' ); + ut.expect( ut3_tester_helper.main_helper.table_to_clob(l_results) ).not_to_be_like( '%suite1_1_level2_pkg.suite1_1_test1_level2 executed%' ); + end; From 9dee7e0ffd4bfee0af948befd4f6b739506c11d1 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Tue, 25 Apr 2023 17:08:42 -0700 Subject: [PATCH 32/34] Update docs --- docs/userguide/running-unit-tests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index b3a974d53..044ffdb35 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -325,7 +325,7 @@ Multiple tags are separated by comma. Tag expressions are boolean expressions created by combining tags with the `!`, `&`, `|` operators. Tag expressions can be grouped using `(` and `)` braces. Grouping tag expressions affects operator precedence. -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. When using `none` be aware that if the suite is tagged it will exclude any tests and children belonging to that suite. +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. When using `none` be aware that if the suite is tagged it will exclude any tests and suites below. | Operator | Meaning | | -------- | --------| From beb9a3a3207e208b7e5e2687b6d4751b4ee705b9 Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Wed, 26 Apr 2023 17:30:32 -0700 Subject: [PATCH 33/34] Resolving PR --- docs/userguide/annotations.md | 2 +- docs/userguide/running-unit-tests.md | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/userguide/annotations.md b/docs/userguide/annotations.md index 4ec6de59d..7c7015872 100644 --- a/docs/userguide/annotations.md +++ b/docs/userguide/annotations.md @@ -1634,7 +1634,7 @@ Tags must follow the below naming convention: - tag cannot start with a dash, e.g. `-some-stuff` is **not** a valid tag - 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` - leading and trailing spaces are ignored in tag name, e.g. `--%tags( tag1 , tag2 )` becomes `tag1` and `tag2` tag names -- tag cannot be one of two reserved words : `none` and `any`, any tags with that will not be considered. +- tag cannot be one of two reserved words: `none` and `any`. `none` and `any` as a tag will be treated as no tag ### Suitepath diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index 044ffdb35..e12fd6b32 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -325,7 +325,13 @@ Multiple tags are separated by comma. Tag expressions are boolean expressions created by combining tags with the `!`, `&`, `|` operators. Tag expressions can be grouped using `(` and `)` braces. Grouping tag expressions affects operator precedence. -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. When using `none` be aware that if the suite is tagged it will exclude any tests and suites below. +Two reserved keywords, `any` and `none`, can be used when creating a tag expression to run tests. +- `any` keyword represents tests and suites with any tags +- `none` keyword represents tests and suites without tags + +These keywords may be combined with other expressions just like normal tags. + +**Note:** When specifying `none`, be aware that it will exclude any tests/suites/contexts contained within a tagged suite. | Operator | Meaning | | -------- | --------| From 46ffe739386b92dc831a46227c17326bb85f459e Mon Sep 17 00:00:00 2001 From: Lukasz Wasylow Date: Thu, 27 Apr 2023 11:48:26 -0700 Subject: [PATCH 34/34] Update note --- docs/userguide/running-unit-tests.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/userguide/running-unit-tests.md b/docs/userguide/running-unit-tests.md index e12fd6b32..2b18a3d3b 100644 --- a/docs/userguide/running-unit-tests.md +++ b/docs/userguide/running-unit-tests.md @@ -331,7 +331,8 @@ Two reserved keywords, `any` and `none`, can be used when creating a tag express These keywords may be combined with other expressions just like normal tags. -**Note:** When specifying `none`, be aware that it will exclude any tests/suites/contexts contained within a tagged suite. +!!! note + When specifying `none`, be aware that it will exclude any tests/suites/contexts contained within a tagged suite. | Operator | Meaning | | -------- | --------|