-
Notifications
You must be signed in to change notification settings - Fork 284
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
thomasspriggs
left a comment
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.
allredj
left a comment
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
| \*******************************************************************/ | ||
|
|
||
| #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
| } | ||
| } | ||
|
|
||
| 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.
allredj
left a comment
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!
|
allredj
left a comment
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
thk123
left a comment
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)
allredj
left a comment
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.