diff --git a/Framework/API/inc/MantidAPI/LogManager.h b/Framework/API/inc/MantidAPI/LogManager.h
index 577e64a38872ac201ac285fdfe81e26f862c9d2c..f5c49e6d9ee07fb1aa068422cd0780cbf5311818 100644
--- a/Framework/API/inc/MantidAPI/LogManager.h
+++ b/Framework/API/inc/MantidAPI/LogManager.h
@@ -182,6 +182,9 @@ public:
   /// Clear the logs
   void clearLogs();
 
+  bool operator==(const LogManager &other) const;
+  bool operator!=(const LogManager &other) const;
+
 protected:
   /// Load the run from a NeXus file with a given group name
   void loadNexus(::NeXus::File *file,
diff --git a/Framework/API/inc/MantidAPI/Run.h b/Framework/API/inc/MantidAPI/Run.h
index 747140a27e9d4c96765150c40a2b6183aab6b7a6..568c25dbdce5ac9da5f4e207d8c240af1dfa320e 100644
--- a/Framework/API/inc/MantidAPI/Run.h
+++ b/Framework/API/inc/MantidAPI/Run.h
@@ -41,6 +41,8 @@ public:
   Run(const Run &other);
   ~Run();
   Run &operator=(const Run &other);
+  bool operator==(const Run &other);
+  bool operator!=(const Run &other);
 
   /// Clone
   boost::shared_ptr<Run> clone();
diff --git a/Framework/API/src/LogManager.cpp b/Framework/API/src/LogManager.cpp
index 4f6b9d41c5d6e3b56e608c6ebe57b7305b09bd47..451dbe145c054f9b8410274373ae344359de9d75 100644
--- a/Framework/API/src/LogManager.cpp
+++ b/Framework/API/src/LogManager.cpp
@@ -549,6 +549,14 @@ void LogManager::loadNexus(::NeXus::File *file,
  */
 void LogManager::clearLogs() { m_manager->clear(); }
 
+bool LogManager::operator==(const LogManager &other) const {
+  return *m_manager == *(other.m_manager);
+}
+
+bool LogManager::operator!=(const LogManager &other) const {
+  return *m_manager != *(other.m_manager);
+}
+
 //-----------------------------------------------------------------------------------------------------------------------
 // Private methods
 //-----------------------------------------------------------------------------------------------------------------------
diff --git a/Framework/API/src/Run.cpp b/Framework/API/src/Run.cpp
index f178c78c5eaf7df021bfa2fd66c6071e1201068a..7818d5770453811dde3a64d40b6f101d658d5620 100644
--- a/Framework/API/src/Run.cpp
+++ b/Framework/API/src/Run.cpp
@@ -62,6 +62,14 @@ Run &Run::operator=(const Run &other) {
   return *this;
 }
 
+bool Run::operator==(const Run &other) {
+  return *m_goniometer == *other.m_goniometer &&
+         LogManager::operator==(other) &&
+         this->m_histoBins == other.m_histoBins;
+}
+
+bool Run::operator!=(const Run &other) { return !this->operator==(other); }
+
 boost::shared_ptr<Run> Run::clone() {
   auto clone = boost::make_shared<Run>();
   for (auto property : this->m_manager->getProperties()) {
diff --git a/Framework/API/test/LogManagerTest.h b/Framework/API/test/LogManagerTest.h
index c047bd8e1e057e7196d78b95760644e810fd1ff1..9710ebb4fab940a10fe48852ea98f9dd06fc30ae 100644
--- a/Framework/API/test/LogManagerTest.h
+++ b/Framework/API/test/LogManagerTest.h
@@ -28,6 +28,7 @@ namespace {
 class ConcreteProperty : public Property {
 public:
   ConcreteProperty() : Property("Test", typeid(int)) {}
+  ConcreteProperty(std::string name) : Property(name, typeid(int)) {}
   ConcreteProperty *clone() const override {
     return new ConcreteProperty(*this);
   }
@@ -35,15 +36,21 @@ public:
   std::string getDefault() const override {
     return "getDefault() is not implemented in this class";
   }
-  std::string value() const override { return "Nothing"; }
+  std::string value() const override { return m_value; }
   Json::Value valueAsJson() const override { return Json::Value(); }
-  std::string setValue(const std::string &) override { return ""; }
+  std::string setValue(const std::string &value) override {
+    m_value = value;
+    return m_value;
+  }
   std::string setValueFromJson(const Json::Value &) override { return ""; }
   std::string setValueFromProperty(const Property &) override { return ""; }
   std::string setDataItem(const boost::shared_ptr<DataItem>) override {
     return "";
   }
   Property &operator+=(Property const *) override { return *this; }
+
+private:
+  std::string m_value = "Nothing";
 };
 
 template <typename T>
@@ -185,7 +192,7 @@ public:
     runInfo.addProperty(p);
 
     TS_ASSERT_EQUALS(runInfo.getMemorySize(),
-                     sizeof(ConcreteProperty) + sizeof(void *));
+                     p->getMemorySize() + sizeof(Property *));
   }
 
   void test_GetTimeSeriesProperty_Returns_TSP_When_Log_Exists() {
@@ -530,6 +537,48 @@ public:
     run3.loadNexus(th.file.get(), "");
   }
 
+  void test_operator_equals() {
+    LogManager a;
+    LogManager b;
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    b.addProperty(std::make_unique<ConcreteProperty>());
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equals_when_number_of_entries_differ() {
+    LogManager a;
+    LogManager b;
+    a.addProperty(std::make_unique<ConcreteProperty>("a1"));
+    b.addProperty(std::make_unique<ConcreteProperty>("b1"));
+    b.addProperty(std::make_unique<ConcreteProperty>("b2"));
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equals_when_values_differ() {
+    LogManager a;
+    LogManager b;
+    auto prop1 = std::make_unique<ConcreteProperty>();
+    auto prop2 = std::make_unique<ConcreteProperty>();
+    prop2->setValue("another_value");
+    a.addProperty(std::move(prop1));
+    b.addProperty(std::move(prop2));
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equals_when_keys_differ() {
+    LogManager a;
+    LogManager b;
+    auto prop1 = std::make_unique<ConcreteProperty>("Temp");
+    auto prop2 = std::make_unique<ConcreteProperty>("Pressure");
+    a.addProperty(std::move(prop1));
+    b.addProperty(std::move(prop2));
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
 private:
   template <typename T>
   void doTest_GetPropertyAsSingleValue_SingleType(const T value) {
diff --git a/Framework/API/test/RunTest.h b/Framework/API/test/RunTest.h
index a41f319a7eae852b0a4d9b5ebdca618e2a06d844..d9489980074ef7997216ea2a0de1a61c728ba23b 100644
--- a/Framework/API/test/RunTest.h
+++ b/Framework/API/test/RunTest.h
@@ -609,6 +609,65 @@ public:
     TS_ASSERT_DELTA(run3.getProtonCharge(), 1.234, 1e-5);
   }
 
+  void test_equals_when_runs_empty() {
+    Run a{};
+    Run b{a};
+    TS_ASSERT_EQUALS(a, b);
+  }
+
+  void test_equals_when_runs_identical() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    const DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equal_when_histogram_bin_boundaries_differ() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    b.storeHistogramBinBoundaries({0, 2, 3, 4});
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equal_when_properties_differ() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    b.removeProperty("Test");
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
+  void test_not_equal_when_goniometer_differ() {
+    Run a{};
+    a.addProperty(std::make_unique<ConcreteProperty>());
+    DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    a.setGoniometer(Goniometer{rotation_x}, false /*do not use log angles*/);
+    a.storeHistogramBinBoundaries({1, 2, 3, 4});
+    Run b{a};
+    Mantid::Kernel::DblMatrix rotation_y{
+        {0, 0, 1, 0, 1, 0, -1, 0, 0}}; // 90 degrees around y axis
+    b.setGoniometer(Goniometer{rotation_y}, false /*do not use log angles*/);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
+
 private:
   /// Testing bins
   std::vector<double> m_test_energy_bins;
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h b/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h
index e09c1b7301e6ba95ea4f81b8f1150747d972a45f..86069b5781d9e86706a2de8c58b45fbe47c4a88f 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h
@@ -97,6 +97,8 @@ public:
   void loadNexus(::NeXus::File *file, const std::string &group);
   /// the method reports if the goniometer was defined with some parameters
   bool isDefined() const;
+  bool operator==(const Goniometer &other) const;
+  bool operator!=(const Goniometer &other) const;
 
 private:
   /// Global rotation matrix of the goniometer
diff --git a/Framework/Geometry/src/Instrument/Goniometer.cpp b/Framework/Geometry/src/Instrument/Goniometer.cpp
index e2dd92bd4764f54b2ec037d0586a12a739f0c9fe..4fda260a72865f7c6e8859db079d7ad0e6d17318 100644
--- a/Framework/Geometry/src/Instrument/Goniometer.cpp
+++ b/Framework/Geometry/src/Instrument/Goniometer.cpp
@@ -92,6 +92,14 @@ void Goniometer::setR(Kernel::DblMatrix rot) {
 /// Function reports if the goniometer is defined
 bool Goniometer::isDefined() const { return initFromR || (!motors.empty()); }
 
+bool Goniometer::operator==(const Goniometer &other) const {
+  return this->R == other.R;
+}
+
+bool Goniometer::operator!=(const Goniometer &other) const {
+  return this->R != other.R;
+}
+
 /// Return information about axes.
 /// @return str :: string that contains on each line one motor information (axis
 /// name, direction, sense, angle)
diff --git a/Framework/Geometry/test/GoniometerTest.h b/Framework/Geometry/test/GoniometerTest.h
index 3059f0495ddf205bda8d0f344b50e3083c46251a..0b05a15a8989a5147ecad833339432fb2ed0ac6e 100644
--- a/Framework/Geometry/test/GoniometerTest.h
+++ b/Framework/Geometry/test/GoniometerTest.h
@@ -255,4 +255,24 @@ public:
     // Rotation matrices should be the same after loading
     TS_ASSERT_EQUALS(G2.getR(), G.getR());
   }
+
+  void test_equals_when_identical() {
+    Mantid::Kernel::DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    Goniometer a(rotation_x);
+    Goniometer b(rotation_x);
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_not_equals_when_not_identical() {
+    Mantid::Kernel::DblMatrix rotation_x{
+        {1, 0, 0, 0, 0, -1, 0, 1, 0}}; // 90 degrees around x axis
+    Mantid::Kernel::DblMatrix rotation_y{
+        {0, 0, 1, 0, 1, 0, -1, 0, 0}}; // 90 degrees around y axis
+    Goniometer a(rotation_x);
+    Goniometer b(rotation_y);
+    TS_ASSERT_DIFFERS(a, b);
+    TS_ASSERT(!(a == b));
+  }
 };
diff --git a/Framework/Kernel/inc/MantidKernel/PropertyManager.h b/Framework/Kernel/inc/MantidKernel/PropertyManager.h
index 3314b5d9253e6ef7eaac07e84d48baafcb654b64..fc0d44126012710f6badb73825cdc0c30811c150 100644
--- a/Framework/Kernel/inc/MantidKernel/PropertyManager.h
+++ b/Framework/Kernel/inc/MantidKernel/PropertyManager.h
@@ -108,6 +108,9 @@ public:
   /// Return the property manager serialized as a json object.
   ::Json::Value asJson(bool withDefaultValues = false) const override;
 
+  bool operator==(const PropertyManager &other) const;
+  bool operator!=(const PropertyManager &other) const;
+
 protected:
   friend class PropertyManagerOwner;
 
diff --git a/Framework/Kernel/src/PropertyManager.cpp b/Framework/Kernel/src/PropertyManager.cpp
index fb5da0dddb339f995d210402903af4ad0fdc0177..72e08854f18b810c0332d4c8274997fb62eb986d 100644
--- a/Framework/Kernel/src/PropertyManager.cpp
+++ b/Framework/Kernel/src/PropertyManager.cpp
@@ -583,6 +583,22 @@ std::string PropertyManager::asString(bool withDefaultValues) const {
   return jsonMap;
 }
 
+bool PropertyManager::operator==(const PropertyManager &other) const {
+  if (other.m_properties.size() != m_properties.size())
+    return false;
+  for (const auto & [ key, value ] : m_properties) {
+    if (other.m_properties.count(key) != 1)
+      return false;
+    if (*other.m_properties.at(key) != *value)
+      return false;
+  }
+  return true;
+}
+
+bool PropertyManager::operator!=(const PropertyManager &other) const {
+  return !this->operator==(other);
+}
+
 //-----------------------------------------------------------------------------------------------
 /** Get a property by name
  *  @param name :: The name of the property (case insensitive)
diff --git a/Framework/Kernel/test/PropertyManagerTest.h b/Framework/Kernel/test/PropertyManagerTest.h
index b20f36210793a57905352f382e9b71a49c2837c9..2f95d6edc2f2ccdc0444eb93d5743669a54cb525 100644
--- a/Framework/Kernel/test/PropertyManagerTest.h
+++ b/Framework/Kernel/test/PropertyManagerTest.h
@@ -659,6 +659,43 @@ public:
     TS_ASSERT_EQUALS(i, 33);
   }
 
+  void test_operator_equals_same_contents() {
+    PropertyManager a;
+    PropertyManager b;
+
+    a.declareProperty("Prop1", 10);
+    b.declareProperty("Prop1", 10);
+    TS_ASSERT_EQUALS(a, b);
+    TS_ASSERT(!(a != b));
+  }
+
+  void test_operator_not_equals_different_sizes() {
+    PropertyManager a;
+    PropertyManager b;
+
+    b.declareProperty("Prop1", 10);
+    TSM_ASSERT_DIFFERS("Unequal sizes", a, b);
+    TSM_ASSERT("Unequal sizes", !(a == b));
+  }
+
+  void test_operator_not_equals_different_keys() {
+    PropertyManager a;
+    PropertyManager b;
+    a.declareProperty("Prop1", 1);
+    b.declareProperty("Prop2", 1);
+    TSM_ASSERT_DIFFERS("Different Keys", a, b);
+    TSM_ASSERT("Different Keys", !(a == b));
+  }
+
+  void test_operator_not_equals_different_vaues() {
+    PropertyManager a;
+    PropertyManager b;
+    a.declareProperty("Prop1", 1);
+    b.declareProperty("Prop1", 2);
+    TSM_ASSERT_DIFFERS("Different Values", a, b);
+    TSM_ASSERT("Different Values", !(a == b));
+  }
+
 private:
   std::unique_ptr<PropertyManagerHelper> manager;
 };