Unverified Commit dfc836e0 authored by Jesse Mapel's avatar Jesse Mapel Committed by GitHub
Browse files

Added comparison for JSONs within a tolerance (#5051)

parent fb3884d1
Loading
Loading
Loading
Loading
+71 −0
Original line number Diff line number Diff line
#include <gtest/gtest.h>

#include <nlohmann/json.hpp>

#include "TestUtilities.h"

using json = nlohmann::json;
using namespace Isis;
using namespace std;

TEST(AssertJsonsNear, BasicComparisons) {
  json testJson1 = {
      {"pi", 3.14},
      {"array", {1, 0, 2}},
      {"nested_array", {{1, 2}, {3, 4}, {5, 6}}},
      {"object", {
        {"one", 1},
        {"two", "2"}
      }}
  };
  EXPECT_PRED_FORMAT3(AssertJsonsNear, testJson1, testJson1, 1);

  json testJson2 = testJson1;
  testJson2["new_value"] = "new";
  EXPECT_FALSE(AssertJsonsNear("json1", "json2", "tolerance", testJson1, testJson2, 1));
  EXPECT_FALSE(AssertJsonsNear("json2", "json1", "tolerance", testJson2, testJson1, 1));

  json testJson3 = testJson1;
  testJson3["pi"] = "3.14";
  EXPECT_FALSE(AssertJsonsNear("json1", "json3", "tolerance", testJson1, testJson3, 1));

  json testJson4 = testJson1;
  testJson4["array"].push_back(3);
  EXPECT_FALSE(AssertJsonsNear("json1", "json4", "tolerance", testJson1, testJson4, 1));

  json testJson5 = testJson1;
  testJson5["object"]["one"] = "1";
  EXPECT_FALSE(AssertJsonsNear("json1", "json5", "tolerance", testJson1, testJson5, 1));

  json testJson6 = testJson1;
  testJson6["nested_array"][1].push_back(-1);
  EXPECT_FALSE(AssertJsonsNear("json1", "json6", "tolerance", testJson1, testJson6, 1));
}


TEST(AssertJsonsNear, Tolerance) {
  json testJson1 = {
      {"pi", 3.14},
      {"array", {1, 0, 2}},
      {"nested_array", {{1, 2}, {3, 4}, {5, 6}}},
      {"object", {
        {"one", 1},
        {"two", "2"}
      }}
  };

  json testJson2 = testJson1;
  testJson2["pi"] = 3;
  EXPECT_TRUE(AssertJsonsNear("json1", "json2", "1", testJson1, testJson2, 1));
  EXPECT_FALSE(AssertJsonsNear("json1", "json2", "0.1", testJson1, testJson2, 0.1));

  json testJson3 = testJson1;
  testJson3["nested_array"][2][1] = 5.5;
  EXPECT_TRUE(AssertJsonsNear("json1", "json3", "1", testJson1, testJson3, 1));
  EXPECT_FALSE(AssertJsonsNear("json1", "json3", "0.1", testJson1, testJson3, 0.1));

  json testJson4 = testJson1;
  testJson4["object"]["one"] = 0.5;
  EXPECT_TRUE(AssertJsonsNear("json1", "json3", "1", testJson1, testJson4, 1));
  EXPECT_FALSE(AssertJsonsNear("json1", "json3", "0.1", testJson1, testJson4, 0.1));
}
 No newline at end of file
+13 −11
Original line number Diff line number Diff line
@@ -48,8 +48,9 @@ TEST_F(LidarObservationPair, FunctionalTestLrolola2isisTwoImage) {
  ifs >> truthJson;
  ifs.close();


  ASSERT_EQ(testJson, truthJson);
  // If the model changes slightly, then the back-projected image coordinate
  // can change slightly so use a 0.01 pixel tolerance
  EXPECT_PRED_FORMAT3(AssertJsonsNear, testJson, truthJson, 0.01);
}

TEST_F(LidarObservationPair, FunctionalTestLrolola2isisMultipleCsv) {
@@ -87,6 +88,7 @@ TEST_F(LidarObservationPair, FunctionalTestLrolola2isisMultipleCsv) {
  ifs >> truthJson;
  ifs.close();


  ASSERT_EQ(testJson, truthJson);
  // If the model changes slightly, then the back-projected image coordinate
  // can change slightly so use a 0.01 pixel tolerance
  EXPECT_PRED_FORMAT3(AssertJsonsNear, testJson, truthJson, 0.01);
}
+142 −0
Original line number Diff line number Diff line
#include "TestUtilities.h"

#include <cmath>
#include <string>

namespace Isis {

  /**
@@ -198,6 +201,145 @@ namespace Isis {
    return failure;
  }

  /**
   * Helper recursive comparison function for two JSON objects
   * Logic is modified from nlohmann::json::diff function
   */
  std::vector<std::string> compareJsons(
      const nlohmann::json &json1,
      const nlohmann::json &json2,
      std::string jsonPointer,
      double tolerance) {
    std::vector<std::string> differences;
    // Basic check for equality to short-circuit behavior
    if (json1 == json2) {
      return differences;
    }

    // Check types
    // If both are numeric, then don't check the type so we can compare ints to floats
    if (!(json1.is_number() && json2.is_number()) && json1.type() != json2.type()) {
      differences.push_back("JSONs have different types at [" + jsonPointer + "]");
      return differences;
    }

    switch (json1.type()) {
      // Handle arrays
      case nlohmann::detail::value_t::array: {
        // Check for same size
        if (json1.size() != json2.size()) {
          differences.push_back(
              "JSONs have different sized arrays [" + std::to_string(json1.size())
              + "] and [" + std::to_string(json2.size()) + "] at [" + jsonPointer + "]");
          return differences;
        }

        // Check values
        for (size_t i = 0; i < json1.size(); i++) {
          std::string newPointer = jsonPointer + "/" + std::to_string(i);
          std::vector<std::string> tempDiffs = compareJsons(json1[i], json2[i], newPointer, tolerance);
          differences.insert(differences.end(), tempDiffs.begin(), tempDiffs.end());
        }

        break;
      }

      // Handle objects
      case nlohmann::detail::value_t::object: {
        // Check for keys from the first JSON
        for (auto it = json1.cbegin(); it != json1.cend(); ++it) {
          // Check for presence
          if (json2.contains(it.key())) {
            // Check values
            std::string newPointer = jsonPointer + "/" + it.key();
            std::vector<std::string> tempDiffs = compareJsons(it.value(), json2[it.key()], newPointer, tolerance);
            differences.insert(differences.end(), tempDiffs.begin(), tempDiffs.end());
          }
          else {
            differences.push_back(
                "Key [" + it.key() + "] is present in the first JSON but not the second at ["
                + jsonPointer + "]");
          }
        }

        // Check for keys from the second JSON
        // This time only check for presence because if they are present in the first JSON
        // we have already checked their value
        for (auto it = json2.cbegin(); it != json2.cend(); ++it) {
          if (!json1.contains(it.key())) {
            differences.push_back(
                "Key [" + it.key() + "] is present in the second JSON but not the first at ["
                + jsonPointer + "]");
          }
        }

        break;
      }

      // Handle numeric types
      case nlohmann::detail::value_t::number_integer:
      case nlohmann::detail::value_t::number_unsigned:
      case nlohmann::detail::value_t::number_float: {
        double numDiff = abs(json1.get<double>() - json2.get<double>());
        if (numDiff > tolerance) {
          differences.push_back(
                "Values [" + json1.dump() + "] and [" + json2.dump() + "] differ by ["
                + std::to_string(numDiff) + "] which is greater than tolerance ["
                + std::to_string(tolerance) + "] at [" + jsonPointer + "]");
        }

        break;
      }

      // Handle the rest
      case nlohmann::detail::value_t::null:
      case nlohmann::detail::value_t::string:
      case nlohmann::detail::value_t::boolean:
      case nlohmann::detail::value_t::binary:
      case nlohmann::detail::value_t::discarded:
      default: {
        // This "if" is redundant with the short-circuit check but is included for clarity/safety
        if (json1 != json2) {
          differences.push_back(
                "Values [" + json1.dump() + "] and [" + json2.dump() + "] differ at ["
                + jsonPointer + "]");
        }

        break;
      }
    }

    return differences;
  }


  /**
   * Asserts that two JSON objects are the same except for numerical values are within
   * a given tolerance.
   */
  ::testing::AssertionResult AssertJsonsNear(
      const char* json1_expr,
      const char* json2_expr,
      const char* tolerance_expr,
      const nlohmann::json &json1,
      const nlohmann::json &json2,
      double tolerance) {

    std::vector<std::string> differences = compareJsons(json1, json2, std::string(""), tolerance);

    if (differences.size() > 0) {
      ::testing::AssertionResult failure = ::testing::AssertionFailure()
          << "JSONs " << json1_expr << " and " << json2_expr << " are different within a tolerance of "
          << tolerance_expr << std::endl;
      for (size_t i = 0; i < differences.size(); i++) {
        failure << differences[i] << std::endl;
      }
      return failure;
    }

    return ::testing::AssertionSuccess();
  }

  // Check to see if a QString contains only numeric values.
  bool isNumeric(QString str){
    QRegExp re("-*\\d*.*\\d*");
+16 −0
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@

#include "csm.h"

#include <nlohmann/json.hpp>

#include "FileName.h"
#include "IException.h"
#include "PvlGroup.h"
@@ -59,6 +61,20 @@ namespace Isis {
      const std::vector<double> &vec2,
      double tolerance);

  std::vector<std::string> compareJsons(
      const nlohmann::json &json1,
      const nlohmann::json &json2,
      std::string jsonPointer,
      double tolerance);

  ::testing::AssertionResult AssertJsonsNear(
      const char* json1_expr,
      const char* json2_expr,
      const char* tolerance_expr,
      const nlohmann::json &json1,
      const nlohmann::json &json2,
      double tolerance);

  bool isNumeric(QString str);
  void compareCsvLine(CSVReader::CSVAxis csvLine, QString headerStr, int initialIndex=0);
  void compareCsvLine(CSVReader::CSVAxis csvLine, CSVReader::CSVAxis csvLine2, int initialIndex=0,