From cc820e5f7636cd95575c474e1435c9b45c637b73 Mon Sep 17 00:00:00 2001
From: Sylvester Joosten <sjoosten@anl.gov>
Date: Tue, 22 Dec 2020 03:45:35 +0000
Subject: [PATCH] Added new testing API

---
 util/benchmark.hh | 115 ++++++++++++++++++++++++++++++++++++++++++++++
 util/exception.hh |  23 ++++++++++
 2 files changed, 138 insertions(+)
 create mode 100644 util/benchmark.hh
 create mode 100644 util/exception.hh

diff --git a/util/benchmark.hh b/util/benchmark.hh
new file mode 100644
index 00000000..050e6c64
--- /dev/null
+++ b/util/benchmark.hh
@@ -0,0 +1,115 @@
+#ifndef BENCHMARK_LOADED
+#define BENCHMARK_LOADED
+
+#include "exception.hh"
+#include <fmt/core.h>
+#include <fstream>
+#include <iostream>
+#include <nlohmann/json.hpp>
+#include <string>
+
+// Bookkeeping of test data to store data of one or more tests in a json file to
+// facilitate future accounting.
+//
+// Usage Example 1 (single test):
+// ==============================
+// 1. define our test
+//      juggler_util::test_data test1{
+//        {{"name", "example_test"},
+//        {"title", "Example Test"},
+//        {"description", "This is an example of a test definition"},
+//        {"quantity", "efficiency"},
+//        {"target", "1"}}};
+// 2. set pass/fail/error status and return value (in this case .99)
+//      test1.pass(0.99)
+// 3. write our test data to a json file
+//      juggler_util::write_test_data(test1, "test1.json");
+//
+// Usage Example 2 (multiple tests):
+// =================================
+// 1. define our tests
+//      juggler_util::test_data test1{
+//        {{"name", "example_test"},
+//        {"title", "Example Test"},
+//        {"description", "This is an example of a test definition"},
+//        {"quantity", "efficiency"},
+//        {"target", "1"}}};
+//      juggler_util::test_data test2{
+//        {{"name", "another_test"},
+//        {"title", "Another example Test"},
+//        {"description", "This is a second example of a test definition"},
+//        {"quantity", "resolution"},
+//        {"target", "3."}}};
+// 2. set pass/fail/error status and return value (in this case .99)
+//      test1.fail(10)
+// 3. write our test data to a json file
+//      juggler_util::write_test_data({test1, test2}, "test.json");
+
+// Namespace for utility scripts, FIXME this should be part of an independent
+// library
+namespace juggler_util {
+
+struct test_definition_error : exception {
+  test_definition_error(std::string_view msg)
+      : exception(msg, "test_definition_error") {}
+};
+
+// Wrapper for our test data json, with three methods to set the status
+// after test completion (to pass, fail or error). The default value
+// is error.
+// The following fields should be defined in the definitions json
+// for the test to make sense:
+//  - name: unique identifier for this test
+//  - title: Slightly more verbose identifier for this test
+//  - description: Concise description of what is tested
+//  - quantity: What quantity is tested? Unites of value/target
+//  - target: Target value of <quantity> that we want to reach
+//  - value: Actual value of <quantity>
+//  - weight: Weight for this test (this is defaulted to 1.0 if not specified)
+//  - result: pass/fail/error
+struct test_data {
+  test_data(nlohmann::json definition) : json{std::move(definition)} {
+    json["weight"] = 1.0;
+    // initialize with error (as we don't have a value yet)
+    error();
+    // Check that all required fields are present
+    for (const auto& field : {"name", "title", "description", "quantity",
+                              "target", "value", "weight", "result"}) {
+      if (json.find(field) == json.end()) {
+        throw test_definition_error{
+            fmt::format("Error in test definition: field '{}' missing", field)};
+      }
+    }
+  }
+  // Set this test to pass/fail/error
+  void pass(double value) { update_result("pass", value); }
+  void fail(double value) { update_result("fail", value); }
+  void error(double value = 0) { update_result("error", value); }
+
+  nlohmann::json json;
+
+private:
+  void update_result(std::string_view status, double value) {
+    json["result"] = status;
+    json["value"] = value;
+  }
+};
+
+void write_test_data(const std::vector<test_data>& data,
+                     const std::string& fname) {
+  nlohmann::json test_data;
+  for (auto& entry : data) {
+    test_data.push_back(entry.json);
+  }
+  std::cout << fmt::format("Writing test data to {}\n", fname);
+  std::ofstream output_file(fname);
+  output_file << std::setw(4) << test_data << "\n";
+}
+void write_test_data(const test_data& data, const std::string& fname) {
+  std::vector<test_data> vtd{data};
+  write_test_data(vtd, fname);
+}
+
+} // namespace juggler_util
+
+#endif
diff --git a/util/exception.hh b/util/exception.hh
new file mode 100644
index 00000000..a443c795
--- /dev/null
+++ b/util/exception.hh
@@ -0,0 +1,23 @@
+#ifndef UTIL_EXCEPTION
+#define UTIL_EXCEPTION
+
+#include <exception>
+#include <string>
+
+namespace juggler_util {
+class exception : public std::exception {
+public:
+  exception(std::string_view msg, std::string_view type = "exception")
+      : msg_{msg}, type_{type} {}
+
+  virtual const char* what() const throw() { return msg_.c_str(); }
+  virtual const char* type() const throw() { return type_.c_str(); }
+  virtual ~exception() throw() {}
+
+private:
+  std::string msg_;
+  std::string type_;
+};
+} // namespace juggler_util
+
+#endif
-- 
GitLab