Skip to content

Commit

Permalink
Add Object.operator== and !=
Browse files Browse the repository at this point in the history
Equality is based on the string representation,
but ignoring the difference between unset attributes
and attributes set to their default value
  • Loading branch information
r-owen committed Jul 6, 2017
1 parent 4c1278f commit d6b1dab
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 4 deletions.
26 changes: 22 additions & 4 deletions include/astshim/Object.h
Expand Up @@ -62,6 +62,23 @@ class Object {
Object &operator=(Object const &) = delete;
Object &operator=(Object &&) = default;

/**
Return True if this and `rhs` are the equal
For two objects be equal, they both must have the same attributes and all contained objects
must be equal. However, this test ignores the difference between attributes that are not set
and attributes that are set to their default value, because that difference is irrelevant
to most behavior.
*/
bool operator==(Object const &rhs) const;

/**
Return True if this and `rhs` are not equal
See operator== for details
*/
bool operator!=(Object const &rhs) const { return !(*this == rhs); };

/**
Construct an @ref Object from a string, using astFromString
*/
Expand Down Expand Up @@ -202,16 +219,17 @@ class Object {
/**
Print a textual description the object to an ostream.
It is provided primarily as an aid to debugging
@param[in, out] os The stream to which to write the string representation.
@param[in] showComments Show comments?
*/
void show(std::ostream &os) const;
void show(std::ostream &os, bool showComments = true) const;

/**
Return a textual description the object as a string.
It is provided primarily as an aid to debugging
@param[in] showComments Show comments?
*/
std::string show() const;
std::string show(bool showComments = true) const;

/**
Has this attribute been explicitly set (and not subsequently cleared)?
Expand Down
2 changes: 2 additions & 0 deletions python/astshim/object.cc
Expand Up @@ -42,6 +42,8 @@ PYBIND11_PLUGIN(object) {

cls.def("__str__", &Object::getClassName);
cls.def("__repr__", [](Object const &self) { return "astshim." + self.getClassName(); });
cls.def("__eq__", &Object::operator==, py::is_operator());
cls.def("__ne__", &Object::operator!=, py::is_operator());

cls.def_property_readonly("className", &Object::getClassName);
cls.def_property("id", &Object::getID, &Object::setID);
Expand Down
16 changes: 16 additions & 0 deletions src/Object.cc
Expand Up @@ -19,6 +19,7 @@
* the GNU General Public License along with this program. If not,
* see <https://www.lsstcorp.org/LegalNotices/>.
*/
#include <algorithm>
#include <functional>
#include <ostream>
#include <sstream>
Expand Down Expand Up @@ -76,6 +77,21 @@ extern "C" void sinkToOstream(const char *text) {

} // anonymous namespace

bool Object::operator==(Object const &rhs) const {
auto thisStr = this->show(false);
auto rhsStr = rhs.show(false);

if (thisStr.size() != rhsStr.size()) {
return false;
}

// replace # with ' ' to ignore difference between unset attributes and those set to the default value
std::replace(thisStr.begin(), thisStr.end(), '#', ' ');
std::replace(rhsStr.begin(), rhsStr.end(), '#', ' ');

return rhsStr == thisStr;
}

std::shared_ptr<Object> Object::_basicFromAstObject(AstObject *rawObj) {
static std::unordered_map<std::string, std::function<std::shared_ptr<Object>(AstObject *)>>
ClassCasterMap = {
Expand Down
24 changes: 24 additions & 0 deletions tests/test_object.py
Expand Up @@ -93,6 +93,30 @@ def test_error_handling(self):
except RuntimeError as e:
self.assertEqual(e.args[0].count("Error"), 1)

def test_equality(self):
"""Test __eq__ and __ne__
"""
# pick a case where we know there is a difference between
# attributes that are unset and those set to the default value
frame1 = astshim.Frame(2)
frameSet1 = astshim.FrameSet(frame1) # base is unset
self.assertFalse(frameSet1.test("Base"))
frameSet2 = astshim.FrameSet(frame1)
frameSet2.base = 1
self.assertTrue(frameSet2.test("Base"))
self.assertTrue(frameSet1 == frameSet2)
self.assertFalse(frameSet1 != frameSet2)
self.assertEqual(frameSet1, frameSet2)

self.assertNotEqual(frameSet1, frameSet1.getInverse())
self.assertEqual(frameSet1, frameSet1.getInverse().getInverse())
self.assertEqual(frameSet1.getInverse(), frameSet2.getInverse())

frame3 = astshim.Frame(2)
frame3.title = "Frame 3"
frameSet3 = astshim.FrameSet(frame3)
self.assertNotEqual(frameSet1, frameSet3)

def test_id(self):
"""Test that ID is *not* transferred to copies"""
obj = astshim.ZoomMap(2, 1.3)
Expand Down

0 comments on commit d6b1dab

Please sign in to comment.