Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Feature/rework matcher #1076

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Feb 4, 2022
Merged

Feature/rework matcher #1076

merged 37 commits into from
Feb 4, 2022

Conversation

lwasylow
Copy link
Member

@lwasylow lwasylow commented Jun 7, 2020

Adding a matcher within_of.
Rework some of the compound expectation to use a pass a matcher to expectation to remove methods from ut_equal_compound.
Implements #77

@lwasylow lwasylow requested a review from jgebal June 7, 2020 14:30
@jgebal
Copy link
Member

jgebal commented Jun 10, 2020

@lwasylow - sorry for not reviewing it yet.
I'll look at it this week.

l_result ut_equal := self;
begin
l_result.options.exclude.add_items(a_items);
l_result.expectation.to_(l_result );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all calls to: l_result.expecatation in this type are assuming that the expectation attribute is set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the number of places this is called, it would be good to delegate exception handling to a helper procedure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lwasylow
I don't really remember why this change was done.
I think it's part of some clean-up operation but not really part of new matcher. Is your memory any better than mine?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jgebal

Went back to old conversations on slack from may 27th 2020 and found this:

To not duplicate old methods from matchers we can change behaviour.
UT_MATCHER is additional attribute of expectation which is set when function is called.
Procedure TO_EQUAL in UT_EXPECTATION works the same.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I've found it. But after 2 years I can't even understand what I had in mind.
I'll put the original (polish) version here, just in case, as the free version of SLACK that we use purges messages history.

Propozycja:
Żeby nie duplikować wszystkich metod z matcherów w ut_Expectation można zmienić to zachowanie w następujący sposób:
ut_matcher a teraz dodatkowy atrybut expectation który jest ustawiany gdy wołana jest funcja.
Procedura to_equal w ut_Expectation działa jak dotychczas - raportuje expecattaion
Funkcja to_equal w ut_Expecattion_compound :
ustawia wartość pola expectation w przekazanym matcher na wartość self
zwraca matcher, tak żeby metchod chaining teraz już używał metod matchera (dzięki temu nie trzeba już duplikować dodatkowych metod w ut_expectation
Matcher może odwołać się teraz do expectation z którego został wywołany.
W procedurze matchera (ostatnia metoda w łańcuchu) self.expecation is null matcher nie woła expecataion, w innym wypadku procedury matcher wołaję metodę to_ przekazując self
W ten sposób składnia:
ut.expect(...).to_( equal(...).incude(...) ) sprawia, że:
matcher nie woła expectation, bo expectation nie jest ustawione w matcher
matcher jest przekazany do expectation i expectation wykonuje sprawdzenie wartości i raportuje
Składnia:
ut.expect(...).to_equal(...).include sprawia że:
to_equal ustawia self jako expectation w matcherze, zwraca matcher i dokonuje się metchod chaining
include wywoływane jest na matcherze i poniewarz jest to procedura i pole expectation jest ustawione, matcher woła metodę ecpectation.to_ przekazując self jako arcument
Powinno działać, ale jest 2 w nocy więc mogę majaczyć

@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities (and Security Hotspot 1 Security Hotspot to review)
Code Smell A 18 Code Smells

89.2% 89.2% Coverage
0.0% 0.0% Duplication

@pesse
Copy link
Member

pesse commented Nov 12, 2020

What's missing here? Maybe I can help

@jgebal jgebal added this to the 3.1.12 milestone Jan 29, 2022
@jgebal
Copy link
Member

jgebal commented Jan 29, 2022

While running the feature tests locally just now, I have noticed a pitfall of using interval datatype as a time distance.
The below block fails with exceptions:

set serverout on;
begin
  ut.expect(date '2022-01-30').to_be_within(INTERVAL '1'  MONTH).of_(date '2022-01-30');
end;
/
begin
  ut.expect(date '2022-05-31').to_be_within(INTERVAL '1'  MONTH).of_(date '2022-05-31');
end;
/
begin
  ut.expect(date '2022-01-30').to_be_within(INTERVAL '1'  MONTH).of_(date '2022-01-30');
end;
Error report -
ORA-01839: date not valid for month specified
ORA-06512: at line 11
ORA-06512: at "UT3.UT_BE_WITHIN_HELPER", line 24
ORA-06512: at "UT3.UT_BE_WITHIN", line 114
ORA-06512: at "UT3.UT_EXPECTATION_BASE", line 26
ORA-06512: at "UT3.UT_BE_WITHIN", line 44
ORA-06512: at line 2
01839. 00000 -  "date not valid for month specified"
*Cause:    
*Action:

Error starting at line : 6 in command -
begin
  ut.expect(date '2022-05-31').to_be_within(INTERVAL '1'  MONTH).of_(date '2022-05-31');
end;
Error report -
ORA-01839: date not valid for month specified
ORA-06512: at line 3
ORA-06512: at "UT3.UT_BE_WITHIN_HELPER", line 24
ORA-06512: at "UT3.UT_BE_WITHIN", line 114
ORA-06512: at "UT3.UT_EXPECTATION_BASE", line 26
ORA-06512: at "UT3.UT_BE_WITHIN", line 44
ORA-06512: at line 2
01839. 00000 -  "date not valid for month specified"
*Cause:    
*Action:

This is because:
January 30 + Interval '1' month, according to Interval maths is February 30th 😞
and
May 31st + Inter

val '1' month, according to Interval maths is June 31st

So, we would really need to rethink the maths and meaning of + 1 month to be more like add_months logic which assumes that if the day of month falls beyond last day of a month, it means it should be last day of a month.
Example:

select add_months(date '2022-05-31',1) from dual;
2022-06-30 12:00:00 AM

It seems that only the interval month logic suffers from that problem.

@jgebal
Copy link
Member

jgebal commented Jan 29, 2022

Well. interval year can also suffer from that problem on leap years.
https://stackoverflow.com/questions/35695175/oracle-db-ora-01839-date-not-valid-for-month-specified-29-02-2016-leap-year

@mathewbutler
Copy link

The stack overflow link references this spec:

http://www.inf.fu-berlin.de/lehre/SS05/19517-V/FolienEtc/sql-foundation-aug94.pdf

Table 11— “Valid values for fields in datetime items” (page 130) states:

DAY - “ Within the range 1 to 31, but further constrained by the value of MONTH and YEAR fields, according to the rules for well-formed dates in the Gregorian calendar.”

This suggests that valid Gregorian calendar days should be the result.

I’d this behaviour of INTERVAL types an Oracle bug?

@lwasylow
Copy link
Member Author

Well. interval year can also suffer from that problem on leap years. https://stackoverflow.com/questions/35695175/oracle-db-ora-01839-date-not-valid-for-month-specified-29-02-2016-leap-year

I think we can work this around as we already have function to extract string from interval. I reckon we can try go this route to translate interval into add_months etc.

@mathewbutler
Copy link

The stack overflow link references this spec:

http://www.inf.fu-berlin.de/lehre/SS05/19517-V/FolienEtc/sql-foundation-aug94.pdf

Table 11— “Valid values for fields in datetime items” (page 130) states:

DAY - “ Within the range 1 to 31, but further constrained by the value of MONTH and YEAR fields, according to the rules for well-formed dates in the Gregorian calendar.”

This suggests that valid Gregorian calendar days should be the result.

I’d this behaviour of INTERVAL types an Oracle bug?

More digging and Oracle state this not a bug - just the way INTERVAL works. There’s an asktom from 2005 - Mr Kyte covers it all:

https://asktom.oracle.com/pls/apex/f?p=100:11:0::::p11_question_id:1157035034361

@jgebal
Copy link
Member

jgebal commented Jan 30, 2022

I think I've found a solution that would still rely on internal oracle logic related to intervals.
We currently use the below pseudo-code

declare
  l_result boolean;
begin
  l_result := 
    a_actual  between (a_expected - a_distance) and (a_expected + a_distance);
end;

The above results in problems when we calculate: (a_expected -/+ a_distance) for a_distance expressed as YM Interval when the calculation would result in invalid day of month like below:

select (timestamp '2022-01-30 14:45:56.123456'  + interval '0-1' year to month) from dual;

However we can express the same intent with a different formula:

declare
  l_result boolean;
begin
  l_result := ( least( a_actual, a_expected ) - greatest(a_actual, a_expected) ) year to month <= a_distance;
end;

That way, we avoid the problems with invalid dates rather than adding/subtracting interval to date, we calculate YM distance between dates and then compare it to expected max distance.

For date datatype of expected & actual this would look like below:

declare
  l_result boolean;
begin
  l_result := ( cast(least( a_actual, a_expected ) as timestamp) - cast( greatest(a_actual, a_expected) as timestamp) ) year to month <= a_distance;
end;

…month

Improved error handling for null values and incompatible datatypes
Improved handling of negative dates (BC)
Improved reporting of interval text value
Improved datatypes to handle large&small precissions for timestamps
@jgebal
Copy link
Member

jgebal commented Jan 30, 2022

I have implemented required fixes.
@lwasylow @mathewbutler - would you mind having a look at latest commit?

@jgebal
Copy link
Member

jgebal commented Jan 30, 2022

TODO - review readme and documentation update to assure that the matcher is well explained along with it's limitations and behaviour to make sure there is no confusion when folks will use it.

@jgebal jgebal force-pushed the feature/rework_matcher branch from 7c121ab to 4e0c703 Compare January 31, 2022 21:30
@jgebal jgebal force-pushed the feature/rework_matcher branch from 4e0c703 to 86e84c8 Compare January 31, 2022 21:40
Added human-readable reporting of tests run-time into `ut_documentation_reporter`.
@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

94.5% 94.5% Coverage
0.0% 0.0% Duplication

@jgebal
Copy link
Member

jgebal commented Jan 31, 2022

Question:
Should the to_be_within matcher allow for comparison of

  • timestamp vs timestamp with time zone
  • timestamp vs timestamp with local time zone
  • date vs timestamp
  • etc.

That is, should we relax the strict datatype check that is present for the to_equal and allow for more relaxed check?
I think now is the time for this discussion before we merge and release this new matcher.

@jgebal
Copy link
Member

jgebal commented Feb 2, 2022

I have fixed 2 issues with pct matcher:

  1. divisor=0 on expected = 0
  2. matcher worked only one way.
    ut.expect(99).to_be_within_pct(1).of_(100) was giving SUCCESS
    ut.expect(101).to_be_within_pct(1).of_(100) was giving FAILURE

With the change introduced now we will have:

begin
  ut3_develop.ut.expect(99).to_be_within_pct(1).of_(100);
  ut3_develop.ut.expect(101).to_be_within_pct(1).of_(100);
  ut3_develop.ut.expect(99).to_be_within_pct(1).of_(100);
  ut3_develop.ut.expect(101).to_be_within_pct(1).of_(100);
  ut3_develop.ut.expect(0).to_be_within_pct(1).of_(0);
  ut3_develop.ut.expect(0).to_be_within_pct(100).of_(1);
  ut3_develop.ut.expect( 1.0001 ).to_be_within_pct( -0.01 ).of_(1);
end;
/

SUCCESS
  Actual: 99 (number) was expected to be within 1 % of 100 (number)
SUCCESS
  Actual: 101 (number) was expected to be within 1 % of 100 (number)
SUCCESS
  Actual: 99 (number) was expected to be within 1 % of 100 (number)
SUCCESS
  Actual: 101 (number) was expected to be within 1 % of 100 (number)
SUCCESS
  Actual: 0 (number) was expected to be within 1 % of 0 (number)
SUCCESS
  Actual: 0 (number) was expected to be within 100 % of 1 (number)
SUCCESS
  Actual: 1.0001 (number) was expected to be within -.01 % of 1 (number)

@lwasylow

…nteger and cannot be used to express a decimal distace value.

Improved tests coverage for various scenarios.
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 2, 2022

Please retry analysis of this Pull-Request directly on SonarCloud.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 2, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

95.9% 95.9% Coverage
0.0% 0.0% Duplication

@lwasylow lwasylow linked an issue Feb 3, 2022 that may be closed by this pull request
@jgebal jgebal merged commit b0a2aab into develop Feb 4, 2022
@jgebal jgebal deleted the feature/rework_matcher branch February 4, 2022 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add be_within 'fuzzy' matcher
4 participants