Skip to content
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

assertEquals unexpectedly fails on equivalent strings with decimals ('0', '0.00') #3185

Closed
IanVS opened this issue Jun 26, 2018 · 23 comments
Closed

Comments

@IanVS
Copy link

IanVS commented Jun 26, 2018

Q A
PHPUnit version 7.3-g2e9ab30
PHP version 7.1.18
Installation Method Composer
composer info | sort antecedent/patchwork 2.1.8 Method redefinition (monkey-... aws/aws-sdk-php 3.10.1 AWS SDK for PHP - Use Amazon... cebe/markdown 1.1.0 A super fast, highly extensi... cocur/slugify v0.7 Converts a string into a slug. composer/ca-bundle dev-master 134242f Lets you find a path to the ... doctrine/annotations dev-master 2497b1f Docblock Annotations Parser doctrine/cache dev-master beb0fa3 Caching library offering an ... doctrine/collections dev-master 42c4039 Collections Abstraction library doctrine/common dev-master a3e240f Common Library for Doctrine ... doctrine/inflector dev-master 40daa06 Common String Manipulations ... doctrine/instantiator dev-master 870a62d A small, lightweight utility... doctrine/lexer dev-master cc709ba Base library for a lexer tha... facebook/fbmock dev-master b90fce5 FBMock is a PHP mocking fram... firebase/php-jwt v5.0.0 A simple library to encode a... friendsofsymfony/rest-bundle 1.8.x-dev 12081a7 This Bundle provides various... giggsey/libphonenumber-for-php 5.9.1 Unofficial PHP Port of Googl... google/apiclient 1.0.5-beta Client library for Google APIs guzzlehttp/guzzle 6.3.0 Guzzle is a PHP HTTP client ... guzzlehttp/promises dev-master 2e3e428 Guzzle promises library guzzlehttp/psr7 dev-master b27e0bd PSR-7 message implementation... hamcrest/hamcrest-php dev-master be5380f This is the PHP port of Hamc... ircmaxell/password-compat 1.0.x-dev 9b99377 A compatibility library for ... jms/metadata 1.6.0 Class/method/property metada... jms/parser-lib dev-master 6067cc6 A library for easily creatin... jms/serializer-bundle 1.5.0 Allows you to easily seriali... jms/serializer dev-master f4683f4 Library for (de-)serializing... league/csv 8.x-dev fa8bc05 Csv data manipulation made e... league/oauth2-client 2.2.1 OAuth 2.0 Client Library maknz/slack 1.7.0 A simple PHP package for sen... mikey179/vfsStream v1.2.0 mockery/mockery 1.0 Mockery is a simple yet flex... monolog/monolog 1.x-dev fd8c787 Sends your logs to files, so... mtdowling/cron-expression v1.0.4 CRON for PHP: Calculate the ... mtdowling/jmespath.php 2.4.0 Declaratively specify how to... myclabs/deep-copy 1.x-dev 3e01bda Create deep copies (clones) ... nutshell/nutshell-billing dev-master 8514786 Billing for Nusthell paragonie/random_compat v2.0.11 PHP 5.x polyfill for random_... phar-io/manifest dev-master 29654cc Component for reading phar.i... phar-io/version 2.0.0 Library for handling version... phpcollection/phpcollection 0.5.0 General-Purpose Collection L... phpdocumentor/reflection-common 1.0.1 Common reflection classes us... phpdocumentor/reflection-docblock 4.3.0 With this component, a libra... phpdocumentor/type-resolver 0.4.0 phpoption/phpoption 1.5.0 Option Type for PHP phpspec/prophecy dev-master 6471ce6 Highly opinionated mocking f... phpunit/dbunit dev-master 22843ee PHPUnit extension for databa... phpunit/php-code-coverage dev-master 8656625 Library that provides collec... phpunit/php-file-iterator dev-master cecbc68 FilterIterator implementatio... phpunit/php-text-template 1.2.1 Simple template engine. phpunit/php-timer dev-master 9ef9968 Utility class for timing phpunit/php-token-stream dev-master 711ca0c Wrapper around PHP's tokeniz... phpunit/phpunit dev-master 2e9ab30 The PHP Unit Testing framework. psr/http-message dev-master f6561bf Common interface for HTTP me... psr/log dev-master 4ebe3a8 Common interface for logging... quickbooks/v3-php-sdk dev-master c69c881 The Official PHP SDK for Qui... recurly/recurly-client dev-master 6f6d82a The PHP client library for t... ruudk/twitter-oauth dev-request-headers 665b1b4 PHP 5.3 version of abraham/t... sabre/vobject 3.5.x-dev 129d805 The VObject library for PHP ... sebastian/code-unit-reverse-lookup dev-master 22f5f5f Looks up which function or m... sebastian/comparator dev-master c9eb293 Provides the functionality t... sebastian/diff dev-master 366541b Diff implementation sebastian/environment dev-master 32c5cba Provides functionality to ha... sebastian/exporter dev-master 2096271 Provides the functionality t... sebastian/global-state dev-master 30367ea Snapshotting of global state sebastian/object-enumerator dev-master 06d95dc Traverses array structures a... sebastian/object-reflector dev-master 7707193 Allows reflection of object ... sebastian/recursion-context dev-master dbe1869 Provides functionality to re... sebastian/resource-operations dev-master 0f9911f Provides a list of PHP built... sebastian/version 2.0.1 Library that helps with mana... segmentio/analytics-php 1.4.2 Segment Analytics PHP Library sensio/distribution-bundle dev-master 51554e1 The base bundle for the Symf... sensio/framework-extra-bundle 2.3.x-dev f9e4c97 This bundle provides a way t... sensio/generator-bundle 2.5.x-dev ab9345f This bundle generates code f... sensiolabs/security-checker 4.1.x-dev d539ccb A security checker for your ... symfony/monolog-bundle 2.x-dev c710da0 Symfony MonologBundle symfony/polyfill-apcu dev-master cec3239 Symfony polyfill backporting... symfony/polyfill-intl-icu dev-master 4aa0b65 Symfony polyfill for intl's ... symfony/polyfill-mbstring dev-master 7c8fae0 Symfony polyfill for the Mbs... symfony/polyfill-php54 dev-master b776342 Symfony polyfill backporting... symfony/polyfill-php55 dev-master 29b1381 Symfony polyfill backporting... symfony/polyfill-php56 dev-master e85ebde Symfony polyfill backporting... symfony/polyfill-php70 dev-master b6482e6 Symfony polyfill backporting... symfony/polyfill-util dev-master 67925d1 Symfony utilities for portab... symfony/psr-http-message-bridge dev-master b209840 PSR HTTP message bridge symfony/security-acl dev-master ab4dfe2 Symfony Security Component -... symfony/symfony v2.8.27 The Symfony PHP framework symfony/yaml 3.3.x-dev 8c7bf1e Symfony Yaml Component theseer/tokenizer 1.1.0 A small library for converti... twig/twig 2.x-dev d8d888e Twig, the flexible, fast, an... twilio/sdk 3.13.1 A PHP wrapper for Twilio's API webmozart/assert dev-master 53927dd Assertions to validate metho... webonyx/graphql-php dev-master eaadae4 A PHP port of GraphQL refere... willdurand/jsonp-callback-validator v1.1.0 JSONP callback validator. willdurand/negotiation 1.x-dev 2a59f23 Content Negotiation tools fo... zendframework/zend-diactoros 1.7.x-dev 89d471c PSR HTTP Message implementat...

I'm a new user, attempting an upgrade of phpunit from 6.3 to 7.3. I found that a few of our tests have started to fail. It seems they shouldn't, based on the statement that assertEquals uses == to check equality.

This fails:

$this->assertEquals('0', '0.00');

But:

php -a
Interactive mode enabled

php > var_dump('0' == '0.00');
php shell code:1:
bool(true)

Is this expected?

@IanVS
Copy link
Author

IanVS commented Jun 26, 2018

I am also getting another (related?) problem when a + sign is in the string, which is exactly what the original issue I linked to said should not fail.

$this->assertEquals('+1', '1');

Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'+1'
+'1'

@IanVS
Copy link
Author

IanVS commented Jun 26, 2018

I think perhaps this is due to the change: #3179

@IanVS
Copy link
Author

IanVS commented Jun 27, 2018

I did a little more investigation, and this started to fail for me on 7.1.5, perhaps due to a major-version upgrade of sebastian/comparator in that version. In fact, I'm now guessing the behavior I'm seeing is due to sebastianbergmann/comparator#58.

And to be clear, I'm not saying this is necessarily a bad change. In fact, it may have found a few bugs in my code that were passing tests before. It was just an unexpected breaking change in a patch version bump.

@sebastianbergmann
Copy link
Owner

ping @Ocramius

@IanVS
Copy link
Author

IanVS commented Jun 27, 2018

@sebastianbergmann For my own knowledge and so that everyone is clear, what is the "expected" result of assertions like
$this->assertEquals('+1', '1'); and $this->assertEquals('0', '0.00');?

Is the contract for assertEquals that if php treats them as equal according to ==, that the assertion should pass? And if you want strict comparison with ===, to use assertSame instead?

@keradus
Copy link
Contributor

keradus commented Jun 27, 2018

no it is not @IanVS ,
change of mine was targetting 7.2.
7.1.4 brought that regression, 7.1.3 works

Both examples are failind due to changes in comparator packages,
as a workaround add "sebastian/comparator": "^2.1", to your composer file, and it will work for you.

On the meantime, I will dig into comparator 3 issue

@keradus
Copy link
Contributor

keradus commented Jun 27, 2018

ah, between I open the page, disappear for meeting, come back and type my answer - new input is here. just saw :D

@keradus
Copy link
Contributor

keradus commented Jun 27, 2018

Is the contract for assertEquals that if php treats them as equal according to ==, that the assertion should pass? And if you want strict comparison with ===, to use assertSame instead?

indeed, at least according to docs ;)

@Ocramius
Copy link
Sponsor Contributor

These are strings: they are to be compared as strings. == is used internally when the types do not match.

@sebastianbergmann For my own knowledge and so that everyone is clear, what is the "expected" result of assertions like
$this->assertEquals('+1', '1'); and $this->assertEquals('0', '0.00');?

The assertion would fail. The correct assertions you can use there are:

  • $self::assertEquals(+1, '1');
  • $self::assertEquals(+1, 1);
  • $self::assertEquals(0, '0.00');
  • $self::assertEquals('0', 0.00);

If both are strings, type juggling is only dangerous, as you may be passing the incorrect type of data from one layer to the next one, and testing would not even catch those.

@Ocramius
Copy link
Sponsor Contributor

unexpected breaking change

This is a really important bugfix.
Yes, that means there's a possible BC break in bad tests, but I may even consider escalating it as security release, because having your tests fail and need adaptation (see #3185 (comment)) is still better than having a lurking bug not being exposed.

@Ocramius
Copy link
Sponsor Contributor

Note: self::assertEquals(new Comment('0.0'), new Comment('0.')) (yes, this is the golden example - see https://github.com/sebastianbergmann/comparator/pull/58/files for more examples) is not replaceable by self::assertSame(new Comment('0.0'), new Comment('0.')).

@keradus
Copy link
Contributor

keradus commented Jun 27, 2018

yep, it's not. maybe let us introduce assertEqualsObjects that requires to pass 2 objects of same type, and deprecate assertEquals to discourage (and later forbid) relying on type casting ?

@keradus
Copy link
Contributor

keradus commented Jun 29, 2018

@sebastianbergmann , how shall IsEqual constraint works?
Docs https://github.com/sebastianbergmann/phpunit/blob/master/src/Framework/Constraint/IsEqual.php#L19 is not matching implementation, and I believe this brought the confusion in this topic

@keradus
Copy link
Contributor

keradus commented Jul 15, 2018

ping @sebastianbergmann , can you take a look at this, please ?

@sebastianbergmann
Copy link
Owner

Eventually: yes. This will take time (for thinking and discussing).

robbieaverill added a commit to creative-commoners/silverstripe-framework that referenced this issue Jul 18, 2018
PHPUnit 7.1.4 and higher introduces a bug with float comparisons in strings.
The fix until this is resolved is to use PHPUnit 7.1.3.

Reference: sebastianbergmann/phpunit#3185

Also updates the phpunit config file to match the schema and imports a
missing exception namespace.
@stale
Copy link

stale bot commented Sep 13, 2018

This issue has been automatically marked as stale because it has not had activity within the last 60 days. It will be closed after 7 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Sep 13, 2018
@IanVS
Copy link
Author

IanVS commented Sep 17, 2018

Hi, has any further thought been put into this? (I'm mostly jsut commenting to keep the issue open, because it seems there was no resolution.)

@stale stale bot removed the stale label Sep 17, 2018
@Tobion
Copy link
Contributor

Tobion commented Oct 11, 2018

We are also affected by this heavy bc break. While I agree that the edge cases in #58 are bad and should be avoided, I don't think changing assertEquals for this is the correct move. AssertEquals has always been (to me), about loose comparison that potentially includes type juggling. If you know both values are of type string and you want to make sure, the above edge cases do not trigger like '0.0' !== '0', just use assertSame. Basically assertEquals is now a mix of assertEquals and assertSame depending on the types which makes the behavior not obvious at all.

@Ocramius
Copy link
Sponsor Contributor

Ocramius commented Oct 11, 2018 via email

@Tobion
Copy link
Contributor

Tobion commented Oct 11, 2018

In our case we used assertEquals for monetary values (represented as string obviously) and relied on the fact that trailing zeros are ignored (because the internal representation might use more decimals).

So the workaround given my @Ocramius to use $self::assertEquals(0, '0.00') does not work either because you don't represent money as float but as string. So both values are always string but now assertEquals complains about mismatching trailing zeros everywhere.

A solution would be to offer a new assertion that ignores trailing zeros on numeric values represented as string, which is the problem also reported by @IanVS

@stale
Copy link

stale bot commented Dec 10, 2018

This issue has been automatically marked as stale because it has not had activity within the last 60 days. It will be closed after 7 days if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Dec 10, 2018
@stale
Copy link

stale bot commented Dec 17, 2018

This issue has been automatically closed because it has not had activity since it was marked as stale. Thank you for your contributions.

@stale stale bot closed this as completed Dec 17, 2018
junpataleta pushed a commit to junpataleta/moodle that referenced this issue Apr 3, 2019
 Namely:
   - 3rd param of assertEquals() cannot be null.
   - Some incorrect uses of assertNotEmpty().
   - Comparing 2 strings now uses strict (===) evaluation.
       Link: sebastianbergmann/phpunit#3185
     Solution here is one of:
       a) Return to the previous situation, making the comparison
          softer. That can achieved by forcing different types, so
          float == string works.
       b) Changing APIs (both forms and database return strings) to
          perform some conversion to floats. That would make float
          comparison (with floats or strings) to work too.
     The patch here follows the a) approach. Changing all the internals
     for proper float handling sounds excesive when it has been working
     perfectly since ever. So we went the easier route, just getting
     rid of the new === comparisons when needed by changing expectation
     types to float.
stronk7 added a commit to stronk7/moodle that referenced this issue Apr 3, 2019
This is a followup of 85f47ba, where we were relaxing
the (new since phpunit 7.x) strict (===) isEqual()
comparison for strings. Copying the explanations for
easier understanding.

Link: sebastianbergmann/phpunit#3185
Solution here is one of:
  a) Return to the previous situation, making the comparison
     softer. That can achieved by forcing different types, so
     float == string works.
  b) Changing APIs (both forms and database return strings) to
     perform some conversion to floats. That would make float
     comparison (with floats or strings) to work too.
The patch here follows the a) approach. Changing all the internals
for proper float handling sounds excesive when it has been working
perfectly since ever. So we went the easier route, just getting
rid of the new === comparisons when needed by changing expectation
types to float.
@allan-simon
Copy link

allan-simon commented Nov 1, 2023

sorry to dig up this issue, but it's the one google link to

shouldn't a specific assertBcEquals / assertDecimalEquals exists (I'm bad at naming) that under the hood use bccomp
so that it will works also for 7.10 and 7.1 ?
?

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

No branches or pull requests

6 participants