From ec166ca878960e27316cde4ed1e98eca9e2d2f1e Mon Sep 17 00:00:00 2001
From: Owen Arnold <owen.arnold@stfc.ac.uk>
Date: Fri, 23 Oct 2015 13:25:05 +0100
Subject: [PATCH] refs #13989. TDD schenarios for Transfer strat.

Schenarios put in place for Measurement Transfer Strategy. Is now peforming the basic functions we expect.
---
 .../MantidQtCustomInterfaces/Measurement.h    |  19 +-
 MantidQt/CustomInterfaces/src/Measurement.cpp |  26 ++-
 .../src/ReflMeasureTransferStrategy.cpp       |  64 ++++++-
 .../CustomInterfaces/test/MeasurementTest.h   |  14 +-
 .../test/ReflMeasureTransferStrategyTest.h    | 169 +++++++++++++++++-
 5 files changed, 261 insertions(+), 31 deletions(-)

diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h
index 97e52fc9966..2bc00e99f56 100644
--- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h
+++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/Measurement.h
@@ -33,9 +33,12 @@ namespace CustomInterfaces {
 class MANTIDQT_CUSTOMINTERFACES_DLL Measurement {
 
 public:
+  typedef const std::string IDType;
+
   /// Constructor
-  Measurement(const std::string &measurementId, const std::string &subId,
-              const std::string &label, const std::string &type);
+  Measurement(const IDType &measurementId, const IDType &subId,
+              const std::string &label, const std::string &type,
+              const double angle, const std::string &run);
 
   /// Constructional method
   static Measurement InvalidMeasurement();
@@ -47,18 +50,22 @@ public:
   ~Measurement();
 
   bool isUseable() const;
-  std::string id() const;
-  std::string subId() const;
+  IDType id() const;
+  IDType subId() const;
+  std::string run() const;
   std::string type() const;
   std::string label() const;
+  double angle() const;
 
 private:
   /// Constructor
   Measurement();
-  const std::string m_measurementId;
-  const std::string m_subId;
+  const IDType m_measurementId;
+  const IDType m_subId;
   const std::string m_label;
   const std::string m_type;
+  const double m_angle;
+  const std::string m_run;
   bool m_valid;
   /// Not assignable
   Measurement &operator=(const Measurement &);
diff --git a/MantidQt/CustomInterfaces/src/Measurement.cpp b/MantidQt/CustomInterfaces/src/Measurement.cpp
index 0c9f1d54033..148fffc8d2f 100644
--- a/MantidQt/CustomInterfaces/src/Measurement.cpp
+++ b/MantidQt/CustomInterfaces/src/Measurement.cpp
@@ -9,12 +9,15 @@ namespace CustomInterfaces {
  * @param subId
  * @param label
  * @param type
+ * @param angle
+ * @param run
  */
-Measurement::Measurement(const std::string &measurementId,
-                         const std::string &subId, const std::string &label,
-                         const std::string &type)
+Measurement::Measurement(const Measurement::IDType &measurementId,
+                         const Measurement::IDType &subId,
+                         const std::string &label, const std::string &type,
+                         const double angle, const std::string &run)
     : m_measurementId(measurementId), m_subId(subId), m_label(label),
-      m_type(type), m_valid(true) {
+      m_type(type), m_angle(angle), m_run(run), m_valid(true) {
 
   if (m_measurementId.empty()) {
     m_valid = false;
@@ -24,13 +27,15 @@ Measurement::Measurement(const std::string &measurementId,
     m_valid = false;
   } else if (m_type.empty()) {
     m_valid = false;
+  } else if (m_run.empty()) {
+    m_valid = false;
   }
 }
 
 /**
  * Constructor making an invalid Measurement
  */
-Measurement::Measurement() : m_valid(false) {}
+Measurement::Measurement() : m_valid(false), m_angle(0) {}
 
 /**
  * Copy constructor
@@ -38,7 +43,8 @@ Measurement::Measurement() : m_valid(false) {}
  */
 Measurement::Measurement(const Measurement &other)
     : m_measurementId(other.m_measurementId), m_subId(other.m_subId),
-      m_label(other.m_label), m_type(other.m_type), m_valid(other.m_valid) {}
+      m_label(other.m_label), m_type(other.m_type), m_angle(other.m_angle),
+      m_run(other.m_run), m_valid(other.m_valid) {}
 
 /// Destructor
 Measurement::~Measurement() {}
@@ -51,13 +57,17 @@ Measurement Measurement::InvalidMeasurement() { return Measurement(); }
 
 bool Measurement::isUseable() const { return m_valid; }
 
-std::string Measurement::id() const { return m_measurementId; }
+Measurement::IDType Measurement::id() const { return m_measurementId; }
 
-std::string Measurement::subId() const { return m_subId; }
+Measurement::IDType Measurement::subId() const { return m_subId; }
 
 std::string Measurement::label() const { return m_label; }
 
 std::string Measurement::type() const { return m_type; }
 
+double Measurement::angle() const { return m_angle; }
+
+std::string Measurement::run() const { return m_run; }
+
 } // namespace CustomInterfaces
 } // namespace Mantid
diff --git a/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp b/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp
index 31d4c93ddcb..15e655a239e 100644
--- a/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp
+++ b/MantidQt/CustomInterfaces/src/ReflMeasureTransferStrategy.cpp
@@ -1,11 +1,16 @@
 #include "MantidQtCustomInterfaces/ReflMeasureTransferStrategy.h"
 #include "MantidQtCustomInterfaces/ReflMeasurementSource.h"
-
+#include "MantidQtCustomInterfaces/ReflTableSchema.h"
 #include "MantidKernel/ICatalogInfo.h"
 #include "MantidKernel/ProgressBase.h"
 #include "MantidKernel/UserCatalogInfo.h"
 #include <boost/regex.hpp>
 #include <memory>
+#include <vector>
+#include <map>
+#include <utility>
+#include <limits>
+#include <set>
 
 using namespace Mantid::Kernel;
 
@@ -38,26 +43,67 @@ MantidQt::CustomInterfaces::ReflMeasureTransferStrategy::transferRuns(
     const SearchResultMap &searchResults,
     Mantid::Kernel::ProgressBase &progress) {
 
+  typedef std::vector<Measurement> VecSameMeasurement;
+  typedef std::map<Measurement::IDType, VecSameMeasurement>
+      MapGroupedMeasurement;
+
+  MapGroupedMeasurement mapOfMeasurements;
   for (auto it = searchResults.begin(); it != searchResults.end(); ++it) {
     const auto location = it->second.location;
     const auto fuzzyName = it->first;
 
     const auto definedPath = m_catInfo->transformArchivePath(location);
 
+    // This is where we read the meta data.
     Measurement metaData = m_measurementSource->obtain(definedPath, fuzzyName);
-    /*
-    const Poco::File filePath(loadPath);
-    if (filePath.exists() && filePath.isFile()) {
 
-
-    } else {
-        // Load from this path
+    // If the measurement information is not consistent, or could not be
+    // obtained. skip this measurement.
+    if (metaData.isUseable()) {
+      if (mapOfMeasurements.find(metaData.id()) == mapOfMeasurements.end()) {
+        // Start a new group
+        mapOfMeasurements.insert(
+            std::make_pair(metaData.id(), VecSameMeasurement(1, metaData)));
+      } else {
+        // Add to existing group
+        mapOfMeasurements[metaData.id()].push_back(metaData);
+      }
     }
-    */
 
+    // Obtaining metadata could take time.
     progress.report();
   }
-  return std::vector<std::map<std::string, std::string>>(1); // HACK
+
+  // Now flatten everything out into a table-like output
+  std::vector<std::map<std::string, std::string>> output;
+  int nextGroupId = 0;
+
+  for (auto group = mapOfMeasurements.begin(); group != mapOfMeasurements.end();
+       ++group) {
+
+    // Map keyed by subId to index of exisiting subid written.
+    std::map<std::string, int> subIdMap;
+    for (size_t i = 0; i < group->second.size(); ++i) {
+      const Measurement &measurement = group->second[i];
+      if (subIdMap.find(measurement.subId()) != subIdMap.end()) {
+        // We already have that subid.
+        const int rowIndex = subIdMap[measurement.subId()];
+        std::string currentRuns = output[rowIndex][ReflTableSchema::RUNS];
+        output[rowIndex][ReflTableSchema::RUNS] =
+            currentRuns + "+" + measurement.run();
+      } else {
+        std::map<std::string, std::string> row;
+        row[ReflTableSchema::RUNS] = measurement.run();
+        row[ReflTableSchema::ANGLE] = measurement.angle();
+        row[ReflTableSchema::GROUP] = nextGroupId;
+        subIdMap.insert(std::make_pair(measurement.subId(), i));
+        output.push_back(row);
+      }
+    }
+    ++nextGroupId;
+  }
+
+  return output;
 }
 
 ReflMeasureTransferStrategy *ReflMeasureTransferStrategy::clone() const {
diff --git a/MantidQt/CustomInterfaces/test/MeasurementTest.h b/MantidQt/CustomInterfaces/test/MeasurementTest.h
index fba52c20ba9..76fe6539e43 100644
--- a/MantidQt/CustomInterfaces/test/MeasurementTest.h
+++ b/MantidQt/CustomInterfaces/test/MeasurementTest.h
@@ -24,21 +24,25 @@ public:
     const std::string measurementSubId = "s";
     const std::string measurementLabel = "l";
     const std::string measurementType = "t";
+    const double angle = 0.1;
+    const std::string run = "123";
 
     Measurement measurement(measurementId, measurementSubId, measurementLabel,
-                            measurementType);
+                            measurementType, angle, run);
 
     TS_ASSERT(measurement.isUseable());
     TS_ASSERT_EQUALS(measurementId, measurement.id());
     TS_ASSERT_EQUALS(measurementSubId, measurement.subId());
     TS_ASSERT_EQUALS(measurementLabel, measurement.label());
     TS_ASSERT_EQUALS(measurementType, measurement.type());
+    TS_ASSERT_EQUALS(angle, measurement.angle());
+    TS_ASSERT_EQUALS(run, measurement.run());
   }
 
   void test_invalid_construction_when_measurementId_empty() {
 
     Measurement measurement("", "measurementSubId", "measurementLabel",
-                            "measurementType");
+                            "measurementType", 0.1, "111");
 
     TS_ASSERT(!measurement.isUseable());
   }
@@ -46,7 +50,7 @@ public:
   void test_invalid_construction_when_measurementSubId_empty() {
 
     Measurement measurement("measurementId", "", "measurementLabel",
-                            "measurementType");
+                            "measurementType", 0.1, "111");
 
     TS_ASSERT(!measurement.isUseable());
   }
@@ -54,14 +58,14 @@ public:
   void test_invalid_construction_when_label_empty() {
 
     Measurement measurement("measurementId", "measurementSubId", "",
-                            "measurementType");
+                            "measurementType", 0.1, "111");
 
     TS_ASSERT(!measurement.isUseable());
   }
 
   void test_invalid_construction_when_type_empty() {
     Measurement measurement("measurementId", "measurementSubId",
-                            "measurementLabel", "");
+                            "measurementLabel", "", 0.1, "111");
 
     TS_ASSERT(!measurement.isUseable());
   }
diff --git a/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h b/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h
index 71070def522..5cb19b55812 100644
--- a/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h
+++ b/MantidQt/CustomInterfaces/test/ReflMeasureTransferStrategyTest.h
@@ -5,6 +5,7 @@
 #include "ReflMainViewMockObjects.h"
 #include "MantidQtCustomInterfaces/ReflMeasureTransferStrategy.h"
 #include "MantidQtCustomInterfaces/ReflMeasurementSource.h"
+#include "MantidQtCustomInterfaces/ReflTableSchema.h"
 #include <memory>
 #include <gmock/gmock.h>
 #include <utility>
@@ -34,16 +35,18 @@ public:
 
   void test_obtain_single_measurement() {
 
+    // Search result inforation not used in the following since we mock the
+    // return from the measurementSource
     SearchResultMap data;
-    data.insert(std::make_pair<std::string, SearchResult>(
-        "111", SearchResult("descr", "location")));
+    data.insert(
+        std::make_pair<std::string, SearchResult>("111", SearchResult()));
 
     auto mockMeasurementSource = new MockReflMeasurementSource;
     // We expect that we are going to fetch the  measurement data for every
     // search result.
     EXPECT_CALL(*mockMeasurementSource, obtain(_, _))
         .Times(Exactly(data.size()))
-        .WillRepeatedly(Return(Measurement("a", "s_a", "l", "t")));
+        .WillRepeatedly(Return(Measurement("a", "s_a", "l", "t", 0, "111")));
 
     auto mockCatInfo = new MockICatalogInfo;
     // We expect that every location will be translated/transformed to make it
@@ -53,6 +56,7 @@ public:
         .WillRepeatedly(Return(std::string()));
 
     MockProgressBase progress;
+    // We expect a progress update on each transfer
     EXPECT_CALL(progress, doReport(_)).Times(Exactly(data.size()));
 
     ReflMeasureTransferStrategy strategy(
@@ -66,6 +70,165 @@ public:
     TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource));
   }
 
+  void test_when_two_measurement_ids_match_group_them_but_not_others() {
+
+    // Search result inforation not used in the following since we mock the
+    // return from the measurementSource
+    SearchResultMap data;
+    data.insert(
+        std::make_pair<std::string, SearchResult>("111", SearchResult()));
+    data.insert(
+        std::make_pair<std::string, SearchResult>("112", SearchResult()));
+    data.insert(
+        std::make_pair<std::string, SearchResult>("113", SearchResult()));
+
+    auto mockMeasurementSource = new MockReflMeasurementSource;
+    // We are going to return three SearchResults two have the same measurement
+    // id
+    EXPECT_CALL(*mockMeasurementSource, obtain(_, _))
+        .Times(Exactly(data.size()))
+        .WillOnce(Return(Measurement("m1", "s1", "l1", "t1", 0.1, "111")))
+        .WillOnce(Return(Measurement("m1", "s2", "l1", "t1", 0.2, "122")))
+        .WillOnce(Return(Measurement("m2", "s2", "l1", "t1", 0.2, "123")));
+
+    auto mockCatInfo = new MockICatalogInfo;
+    // We expect that every location will be translated/transformed to make it
+    // os specific
+    EXPECT_CALL(*mockCatInfo, transformArchivePath(_))
+        .Times(Exactly(data.size()))
+        .WillRepeatedly(Return(std::string()));
+
+    MockProgressBase progress;
+    // Expect a progress update
+    EXPECT_CALL(progress, doReport(_)).Times(Exactly(data.size()));
+
+    // Make the transfer stragegy
+    ReflMeasureTransferStrategy strategy(
+        std::move(std::unique_ptr<MockICatalogInfo>(mockCatInfo)),
+        std::move(
+            std::unique_ptr<MockReflMeasurementSource>(mockMeasurementSource)));
+
+    // Do the transfer
+    auto transferResult = strategy.transferRuns(data, progress);
+
+    // Check the transfer entries
+    TSM_ASSERT_EQUALS("Wrong number of rows", 3, transferResult.size());
+
+    for (size_t i = 1; i < transferResult.size(); ++i) {
+      TSM_ASSERT_DIFFERS("Runs should be the different for all rows",
+                         transferResult[0][ReflTableSchema::RUNS],
+                         transferResult[i][ReflTableSchema::RUNS]);
+    }
+
+    TSM_ASSERT_EQUALS("Group should be the same for first two rows",
+                      transferResult[0][ReflTableSchema::GROUP],
+                      transferResult[1][ReflTableSchema::GROUP]);
+
+    TSM_ASSERT_DIFFERS("Group should be different for last rows",
+                       transferResult[0][ReflTableSchema::GROUP],
+                       transferResult[2][ReflTableSchema::GROUP]);
+
+    TS_ASSERT(Mock::VerifyAndClear(mockCatInfo));
+    TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource));
+  }
+
+  void test_when_two_measurement_sub_ids_match_combine_rows() {
+
+    // Search result inforation not used in the following since we mock the
+    // return from the measurementSource
+    SearchResultMap data;
+    data.insert(
+        std::make_pair<std::string, SearchResult>("111", SearchResult()));
+    data.insert(
+        std::make_pair<std::string, SearchResult>("112", SearchResult()));
+    data.insert(
+        std::make_pair<std::string, SearchResult>("113", SearchResult()));
+
+    auto mockMeasurementSource = new MockReflMeasurementSource;
+    // All 3 have same measurment id, but we also have 2 with same sub id.
+    EXPECT_CALL(*mockMeasurementSource, obtain(_, _))
+        .Times(Exactly(data.size()))
+        .WillOnce(Return(Measurement("m1", "s1", "l1", "t1", 0.1, "111")))
+        .WillOnce(Return(Measurement("m1", "s1", "l1", "t1", 0.2, "122")))
+        .WillOnce(Return(Measurement("m1", "s2", "l1", "t1", 0.2, "123")));
+
+    auto mockCatInfo = new MockICatalogInfo;
+    // We expect that every location will be translated/transformed to make it
+    // os specific
+    EXPECT_CALL(*mockCatInfo, transformArchivePath(_))
+        .Times(Exactly(data.size()))
+        .WillRepeatedly(Return(std::string()));
+
+    MockProgressBase progress;
+    // Expect a progress update
+    EXPECT_CALL(progress, doReport(_)).Times(Exactly(data.size()));
+
+    // Make the transfer stragegy
+    ReflMeasureTransferStrategy strategy(
+        std::move(std::unique_ptr<MockICatalogInfo>(mockCatInfo)),
+        std::move(
+            std::unique_ptr<MockReflMeasurementSource>(mockMeasurementSource)));
+
+    // Do the transfer
+    auto transferResult = strategy.transferRuns(data, progress);
+
+    // Check the transfer entries
+    TSM_ASSERT_EQUALS("Should have two rows", 2, transferResult.size());
+    TSM_ASSERT_DIFFERS("Runs should be the different for both columns",
+                       transferResult[0][ReflTableSchema::RUNS],
+                       transferResult[1][ReflTableSchema::RUNS]);
+    TSM_ASSERT_EQUALS("Runs should be summed. Sub ids are the same.", "111+122",
+                      transferResult[0][ReflTableSchema::RUNS]);
+    TSM_ASSERT_EQUALS("Other run should be singular.", "123",
+                      transferResult[1][ReflTableSchema::RUNS]);
+
+    for (size_t i = 1; i < transferResult.size(); ++i) {
+      TSM_ASSERT_EQUALS("All should have the same group",
+                        transferResult[0][ReflTableSchema::GROUP],
+                        transferResult[i][ReflTableSchema::GROUP]);
+    }
+
+    TS_ASSERT(Mock::VerifyAndClear(mockCatInfo));
+    TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource));
+  }
+
+  void test_do_not_include_invalid_measurements() {
+    // Search result inforation not used in the following since we mock the
+    // return from the measurementSource
+    SearchResultMap data;
+    data.insert(
+        std::make_pair<std::string, SearchResult>("111", SearchResult()));
+
+    auto mockMeasurementSource = new MockReflMeasurementSource;
+    // We expect that we are going to fetch the  measurement data for every
+    // search result.
+    EXPECT_CALL(*mockMeasurementSource, obtain(_, _))
+        .Times(Exactly(data.size()))
+        .WillRepeatedly(Return(Measurement::InvalidMeasurement()));
+
+    auto mockCatInfo = new MockICatalogInfo;
+    // We expect that every location will be translated/transformed to make it
+    // os specific
+    EXPECT_CALL(*mockCatInfo, transformArchivePath(_))
+        .Times(Exactly(data.size()));
+
+    MockProgressBase progress;
+    // Nothing obtained. No progress to report.
+    EXPECT_CALL(progress, doReport(_)).Times(Exactly(0));
+
+    ReflMeasureTransferStrategy strategy(
+        std::move(std::unique_ptr<MockICatalogInfo>(mockCatInfo)),
+        std::move(
+            std::unique_ptr<MockReflMeasurementSource>(mockMeasurementSource)));
+
+    auto result = strategy.transferRuns(data, progress);
+
+    TSM_ASSERT_EQUALS("Measurements where invalid. Results should be empty.", 0,
+                      result.size());
+    TS_ASSERT(Mock::VerifyAndClear(mockCatInfo));
+    TS_ASSERT(Mock::VerifyAndClear(mockMeasurementSource));
+  }
+
   void test_clone() {
 
     // Sub component ICatalogInfo will be cloned
-- 
GitLab