-
Notifications
You must be signed in to change notification settings - Fork 274
JSON equality operator #4912
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
JSON equality operator #4912
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks useful for writing unit tests.
src/util/json.cpp
Outdated
const auto &right_object = static_cast<const json_objectt &>(right); | ||
const auto yes = [](const std::pair<std::string, jsont> &) { return true; }; | ||
const size_t left_size = narrow<size_t>( | ||
std::count_if(left_object.begin(), left_object.end(), yes)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❔ Can this be done using std::count
, so that you don't need to define yes
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::count
counts occurrences of a specified element, so no.
src/util/json.cpp
Outdated
const auto &right_object = static_cast<const json_objectt &>(right); | ||
const auto yes = [](const std::pair<std::string, jsont> &) { return true; }; | ||
const size_t left_size = narrow<size_t>( | ||
std::count_if(left_object.begin(), left_object.end(), yes)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::distance(left_object.begin(), left_object.end())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or since object
is std::map
, just use .size()
. At present since it is map
you could also use zip
and safely assume the keys occur in the same order, if guarded by a static-assert checking that typeof(object)
really is an (ordered) map.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
json_objectt
is not a map
, it just has some map
-like methods exposed. size
sadly is not one of them.
zip
lives in test-gen AFAIK.
I don't want to rely on the order of elements in the map. We've been shifting towards using unordered_map
in many places, and JSON looks like it could be one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
json_objectt
is not, but its member object
is. zip
lives in https://github.com/diffblue/cbmc/blob/develop/src/util/range.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but if you guard with an appropriate static_assert
(ideally there would be some type trait is_ordered
) then you could be sure this code will be changed as/when we do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
object
is a protected property.
There isn't an is_ordered
trait. Being "ordered" is a runtime property.
If I didn't have to worry about future proofing - zip
is unnecessary - std::equal(b1, e1, b2)
is the tool for this job.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least expose a size
property on json_objectt
rather than hacking around it with the existing interface. The ordered vs. unordered thing I'll pass on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR failed Diffblue compatibility checks (cbmc commit: cfe09dc).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/119292309
Status will be re-evaluated on next push.
Common spurious failures include: the cbmc commit has disappeared in the mean time (e.g. in a force-push); the author is not in the list of contributors (e.g. first-time contributors); compatibility was already broken by an earlier merge.
Codecov Report
@@ Coverage Diff @@
## develop #4912 +/- ##
===========================================
- Coverage 69.19% 69.12% -0.07%
===========================================
Files 1307 1300 -7
Lines 107955 107057 -898
===========================================
- Hits 74700 74007 -693
+ Misses 33255 33050 -205
Continue to review full report at Codecov.
|
src/util/json.cpp
Outdated
@@ -7,8 +7,10 @@ Author: Daniel Kroening, [email protected] | |||
\*******************************************************************/ | |||
|
|||
#include "json.h" | |||
#include <util/narrow.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency: #include "narrow.h"
case jsont::kindt::J_NUMBER: | ||
return left.value == right.value; | ||
case jsont::kindt::J_STRING: | ||
return left.value == right.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall design question: shouldn't we do this more in a visitor-style where each call deriving from jsont
defines each own operator==
and the switch-case here would only do the downcasts and then invoke the more specific equality operators?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, except jsont
much like irept
doesn't allow any virtual functions. Therefore the visitor pattern is not applicable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, hence me saying "visitor-style." But also my comment was intended as a thought, not any kind of hard requirement.
src/util/json.cpp
Outdated
const size_t left_size = | ||
narrow<size_t>(std::distance(left_object.begin(), left_object.end())); | ||
const size_t right_size = | ||
narrow<size_t>(std::distance(right_object.begin(), right_object.end())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do those conversions when afterwards you're only going to use the values for comparing them to each other? I'd go with const auto left_size = std::distance ...
left_object.begin(), | ||
left_object.end(), | ||
[&](const std::pair<std::string, jsont> &pair) { | ||
return right_object[pair.first] == pair.second; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an unnecessary lookup when instead you could just use two iterators over the two containers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but if someone decides to swap std::map
for std::unordered_map
, it could be difficult to debug, so this is more stable/doesn't rely on any invariants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do wonder which containers would exhibit differences in strict equality testing? But I suppose it is conceivable that two std::unordered_map
had their elements constructed in a different order, yet should compare equal. So, yes, keep it as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I still favour
// This relies on ordered maps:
static_assert(std::is_same<typeof(jsont::object), std::map<std::string, jsont>>::value)
unit/json/json_parser.cpp
Outdated
@@ -133,3 +133,110 @@ SCENARIO("Loading JSON files") | |||
} | |||
} | |||
} | |||
|
|||
TEST_CASE("json equality", "[core][test-gen-util][state][type]") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually don't know what those [...][...] parts are good for at all, but this particular entry ("test-gen-util") strikes me as information of no value in this repository.
if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) | ||
and not _IsType(clean_lines, nesting_state, leading_text)): | ||
error(filename, linenum, 'whitespace/braces', 5, | ||
'Missing space before {') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe removing this is the right thing to do, but then we should remove all the code computing leading_text
, trailing_text
above. Or maybe you should just use NOLINTNEXTLINE(whitespace/braces)
in the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-format
already checks for positions and spacing of curly braces and parentheses. The problem stems from the fact this isn't aware of initializer lists/uniform initialization.
std::vector<std::pair<int, int>>{{1, 2}, {3, 4}, {5, 6}};
In the days of C++98, the above wasn't a valid syntax. Curly braces would appear only after if
/for
statements, as block scopes or surround function bodies. The linter expects this to still be the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, in which case the linter should be cleaned properly so as not to leave in place unused code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
Passed Diffblue compatibility checks (cbmc commit: 1a7ca85).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/119297176
Done!
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR failed Diffblue compatibility checks (cbmc commit: 2f63cb8).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/119350287
Status will be re-evaluated on next push.
Common spurious failures include: the cbmc commit has disappeared in the mean time (e.g. in a force-push); the author is not in the list of contributors (e.g. first-time contributors); compatibility was already broken by an earlier merge.
return left.value == right.value; | ||
case jsont::kindt::J_ARRAY: | ||
{ | ||
const auto &left_array = static_cast<const json_arrayt &>(left); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ℹ️ There is a function called to_json_array
at the bottom of this file, which you could re-use instead of adding your own static casts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static_cast
is a feature of C++, and not "my" cast. to_json_array
checks for kind, which in this case is already checked. We'd be doing extra work, so no.
Because it'd be useful to be able to compare whole JSON objects (for instance in unit tests)
Braces are already checked by clang-format, and this rule doesn't allow me to use initializer list constructors/uniform initialization in unit tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code owner review of the cpplint change only (though the other changes lgtm)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
Passed Diffblue compatibility checks (cbmc commit: c70cbab).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/119476532
This is to avoid clang-format silently aborting when encountering Java files, as happened in diffblue#4912. The configuration for Java likely needs tweaking to avoid mass-reformatting, but we might do so in an incremental fashion.
Uh oh!
There was an error while loading. Please reload this page.