Repository: NREL/OpenStudio (language: python)

Issue:
If a reporting measure throws an error (runner.registerError("ERROR TEST"); return false), openstudio doesn't seem to gracefully handle it:
Issue overview
--------------

## Current Behavior

If a reporting measure throws an error (`runner.registerError("ERROR TEST"); return false`), openstudio doesn't seem to gracefully handle it:

```
    terminate called after throwing an instance of 'std::runtime_error'
       what():  Measure ReportSimulationOutput reported an error with [ERROR TEST]
   Aborted (core dumped)
 ```

## Expected Behavior

Should gracefully handle it

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Platform (Operating system, version): all
 - Version of OpenStudio (if using an intermediate build, include SHA): 3.7.0-rc2

### Context

- #5016


Discussion / hints:

Fix patch:
diff --git a/src/cli/RunCommand.cpp b/src/cli/RunCommand.cpp
index a9e01bd926a..cc78ce25983 100644
--- a/src/cli/RunCommand.cpp
+++ b/src/cli/RunCommand.cpp
@@ -134,7 +134,9 @@ namespace cli {
     // Subcommand callback
     app->callback([opt, &ruby, &python] {
       openstudio::OSWorkflow workflow(*opt, ruby, python);
-      workflow.run();
+      if (!workflow.run()) {
+        std::exit(1);
+      }
     });
   }
 
diff --git a/src/utilities/filetypes/RunOptions.cpp b/src/utilities/filetypes/RunOptions.cpp
index 3ce0c817ed8..9c1aabb7cce 100644
--- a/src/utilities/filetypes/RunOptions.cpp
+++ b/src/utilities/filetypes/RunOptions.cpp
@@ -26,31 +26,34 @@ namespace detail {
   Json::Value RunOptions_Impl::toJSON() const {
     Json::Value root;
 
-    // TODO: Why is this only writing if not default? I find it weird
-    if (m_debug) {
+    if (!m_is_debug_defaulted) {
       root["debug"] = m_debug;
     }
 
-    if (m_epjson) {
+    if (!m_is_epjson_defaulted) {
       root["epjson"] = m_epjson;
     }
 
-    if (m_fast) {
+    if (!m_is_fast_defaulted) {
       root["fast"] = m_fast;
     }
 
-    if (m_preserveRunDir) {
+    if (!m_is_preserveRunDir_defaulted) {
       root["preserve_run_dir"] = m_preserveRunDir;
     }
 
-    if (m_skipExpandObjects) {
+    if (!m_is_skipExpandObjects_defaulted) {
       root["skip_expand_objects"] = m_skipExpandObjects;
     }
 
-    if (m_skipEnergyPlusPreprocess) {
+    if (!m_is_skipEnergyPlusPreprocess_defaulted) {
       root["skip_energyplus_preprocess"] = m_skipEnergyPlusPreprocess;
     }
 
+    if (!m_is_cleanup_defaulted) {
+      root["cleanup"] = m_cleanup;
+    }
+
     if (m_customOutputAdapter) {
       root["output_adapter"] = m_customOutputAdapter->toJSON();
     }
@@ -75,14 +78,20 @@ namespace detail {
     return m_debug;
   }
 
+  bool RunOptions_Impl::isDebugDefaulted() const {
+    return m_is_debug_defaulted;
+  }
+
   bool RunOptions_Impl::setDebug(bool debug) {
     m_debug = debug;
+    m_is_debug_defaulted = false;
     onUpdate();
     return true;
   }
 
   void RunOptions_Impl::resetDebug() {
-    m_debug = false;
+    m_debug = DEFAULT_DEBUG;
+    m_is_debug_defaulted = true;
     onUpdate();
   }
 
@@ -90,14 +99,20 @@ namespace detail {
     return m_epjson;
   }
 
+  bool RunOptions_Impl::isEpjsonDefaulted() const {
+    return m_is_epjson_defaulted;
+  }
+
   bool RunOptions_Impl::setEpjson(bool epjson) {
     m_epjson = epjson;
+    m_is_epjson_defaulted = false;
     onUpdate();
     return true;
   }
 
   void RunOptions_Impl::resetEpjson() {
-    m_epjson = false;
+    m_epjson = DEFAULT_EPJSON;
+    m_is_epjson_defaulted = true;
     onUpdate();
   }
 
@@ -105,14 +120,20 @@ namespace detail {
     return m_fast;
   }
 
+  bool RunOptions_Impl::isFastDefaulted() const {
+    return m_is_fast_defaulted;
+  }
+
   bool RunOptions_Impl::setFast(bool fast) {
     m_fast = fast;
+    m_is_fast_defaulted = false;
     onUpdate();
     return true;
   }
 
   void RunOptions_Impl::resetFast() {
-    m_fast = false;
+    m_fast = DEFAULT_FAST;
+    m_is_fast_defaulted = true;
     onUpdate();
   }
 
@@ -120,14 +141,20 @@ namespace detail {
     return m_preserveRunDir;
   }
 
-  bool RunOptions_Impl::setPreserveRunDir(bool preserve) {
-    m_preserveRunDir = preserve;
+  bool RunOptions_Impl::isPreserveRunDirDefaulted() const {
+    return m_is_preserveRunDir_defaulted;
+  }
+
+  bool RunOptions_Impl::setPreserveRunDir(bool preserveRunDir) {
+    m_preserveRunDir = preserveRunDir;
+    m_is_preserveRunDir_defaulted = false;
     onUpdate();
     return true;
   }
 
   void RunOptions_Impl::resetPreserveRunDir() {
-    m_preserveRunDir = false;
+    m_preserveRunDir = DEFAULT_PRESERVERUNDIR;
+    m_is_preserveRunDir_defaulted = true;
     onUpdate();
   }
 
@@ -135,14 +162,20 @@ namespace detail {
     return m_skipExpandObjects;
   }
 
-  bool RunOptions_Impl::setSkipExpandObjects(bool skip) {
-    m_skipExpandObjects = skip;
+  bool RunOptions_Impl::isSkipExpandObjectsDefaulted() const {
+    return m_is_skipExpandObjects_defaulted;
+  }
+
+  bool RunOptions_Impl::setSkipExpandObjects(bool skipExpandObjects) {
+    m_skipExpandObjects = skipExpandObjects;
+    m_is_skipExpandObjects_defaulted = false;
     onUpdate();
     return true;
   }
 
   void RunOptions_Impl::resetSkipExpandObjects() {
-    m_skipExpandObjects = false;
+    m_skipExpandObjects = DEFAULT_SKIPEXPANDOBJECTS;
+    m_is_skipExpandObjects_defaulted = true;
     onUpdate();
   }
 
@@ -150,14 +183,20 @@ namespace detail {
     return m_skipEnergyPlusPreprocess;
   }
 
-  bool RunOptions_Impl::setSkipEnergyPlusPreprocess(bool skip) {
-    m_skipEnergyPlusPreprocess = skip;
+  bool RunOptions_Impl::isSkipEnergyPlusPreprocessDefaulted() const {
+    return m_is_skipEnergyPlusPreprocess_defaulted;
+  }
+
+  bool RunOptions_Impl::setSkipEnergyPlusPreprocess(bool skipEnergyPlusPreprocess) {
+    m_skipEnergyPlusPreprocess = skipEnergyPlusPreprocess;
+    m_is_skipEnergyPlusPreprocess_defaulted = false;
     onUpdate();
     return true;
   }
 
   void RunOptions_Impl::resetSkipEnergyPlusPreprocess() {
-    m_skipEnergyPlusPreprocess = false;
+    m_skipEnergyPlusPreprocess = DEFAULT_SKIPENERGYPLUSPREPROCESS;
+    m_is_skipEnergyPlusPreprocess_defaulted = true;
     onUpdate();
   }
 
@@ -165,14 +204,20 @@ namespace detail {
     return m_cleanup;
   }
 
+  bool RunOptions_Impl::isCleanupDefaulted() const {
+    return m_is_cleanup_defaulted;
+  }
+
   bool RunOptions_Impl::setCleanup(bool cleanup) {
     m_cleanup = cleanup;
+    m_is_cleanup_defaulted = false;
     onUpdate();
     return true;
   }
 
   void RunOptions_Impl::resetCleanup() {
-    m_cleanup = true;
+    m_cleanup = DEFAULT_CLEANUP;
+    m_is_cleanup_defaulted = true;
     onUpdate();
   }
 
@@ -206,6 +251,39 @@ namespace detail {
     onUpdate();
   }
 
+  void RunOptions_Impl::overrideValuesWith(const RunOptions& other) {
+
+    if (!other.isDebugDefaulted()) {
+      setDebug(other.debug());
+    }
+
+    if (!other.isEpjsonDefaulted()) {
+      setEpjson(other.epjson());
+    }
+
+    if (!other.isFastDefaulted()) {
+      setFast(other.fast());
+    }
+
+    if (!other.isPreserveRunDirDefaulted()) {
+      setPreserveRunDir(other.preserveRunDir());
+    }
+
+    if (!other.isSkipExpandObjectsDefaulted()) {
+      setSkipExpandObjects(other.skipExpandObjects());
+    }
+
+    if (!other.isSkipEnergyPlusPreprocessDefaulted()) {
+      setSkipEnergyPlusPreprocess(other.skipEnergyPlusPreprocess());
+    }
+
+    if (!other.isCleanupDefaulted()) {
+      setCleanup(other.cleanup());
+    }
+
+    m_forwardTranslatorOptions.overrideValuesWith(other.forwardTranslatorOptions());
+  }
+
 }  // namespace detail
 
 CustomOutputAdapter CustomOutputAdapter::fromString(std::string customFileName, std::string className, const std::string& options) {
@@ -334,6 +412,10 @@ bool RunOptions::debug() const {
   return getImpl<detail::RunOptions_Impl>()->debug();
 }
 
+bool RunOptions::isDebugDefaulted() const {
+  return getImpl<detail::RunOptions_Impl>()->isDebugDefaulted();
+}
+
 bool RunOptions::setDebug(bool debug) {
   return getImpl<detail::RunOptions_Impl>()->setDebug(debug);
 }
@@ -346,6 +428,10 @@ bool RunOptions::epjson() const {
   return getImpl<detail::RunOptions_Impl>()->epjson();
 }
 
+bool RunOptions::isEpjsonDefaulted() const {
+  return getImpl<detail::RunOptions_Impl>()->isEpjsonDefaulted();
+}
+
 bool RunOptions::setEpjson(bool epjson) {
   return getImpl<detail::RunOptions_Impl>()->setEpjson(epjson);
 }
@@ -358,9 +444,14 @@ bool RunOptions::fast() const {
   return getImpl<detail::RunOptions_Impl>()->fast();
 }
 
+bool RunOptions::isFastDefaulted() const {
+  return getImpl<detail::RunOptions_Impl>()->isFastDefaulted();
+}
+
 bool RunOptions::setFast(bool fast) {
   return getImpl<detail::RunOptions_Impl>()->setFast(fast);
 }
+
 void RunOptions::resetFast() {
   getImpl<detail::RunOptions_Impl>()->resetFast();
 }
@@ -369,8 +460,12 @@ bool RunOptions::preserveRunDir() const {
   return getImpl<detail::RunOptions_Impl>()->preserveRunDir();
 }
 
-bool RunOptions::setPreserveRunDir(bool preserve) {
-  return getImpl<detail::RunOptions_Impl>()->setPreserveRunDir(preserve);
+bool RunOptions::isPreserveRunDirDefaulted() const {
+  return getImpl<detail::RunOptions_Impl>()->isPreserveRunDirDefaulted();
+}
+
+bool RunOptions::setPreserveRunDir(bool preserveRunDir) {
+  return getImpl<detail::RunOptions_Impl>()->setPreserveRunDir(preserveRunDir);
 }
 
 void RunOptions::resetPreserveRunDir() {
@@ -381,8 +476,12 @@ bool RunOptions::skipExpandObjects() const {
   return getImpl<detail::RunOptions_Impl>()->skipExpandObjects();
 }
 
-bool RunOptions::setSkipExpandObjects(bool skip) {
-  return getImpl<detail::RunOptions_Impl>()->setSkipExpandObjects(skip);
+bool RunOptions::isSkipExpandObjectsDefaulted() const {
+  return getImpl<detail::RunOptions_Impl>()->isSkipExpandObjectsDefaulted();
+}
+
+bool RunOptions::setSkipExpandObjects(bool skipExpandObjects) {
+  return getImpl<detail::RunOptions_Impl>()->setSkipExpandObjects(skipExpandObjects);
 }
 
 void RunOptions::resetSkipExpandObjects() {
@@ -393,8 +492,12 @@ bool RunOptions::skipEnergyPlusPreprocess() const {
   return getImpl<detail::RunOptions_Impl>()->skipEnergyPlusPreprocess();
 }
 
-bool RunOptions::setSkipEnergyPlusPreprocess(bool skip) {
-  return getImpl<detail::RunOptions_Impl>()->setSkipEnergyPlusPreprocess(skip);
+bool RunOptions::isSkipEnergyPlusPreprocessDefaulted() const {
+  return getImpl<detail::RunOptions_Impl>()->isSkipEnergyPlusPreprocessDefaulted();
+}
+
+bool RunOptions::setSkipEnergyPlusPreprocess(bool skipEnergyPlusPreprocess) {
+  return getImpl<detail::RunOptions_Impl>()->setSkipEnergyPlusPreprocess(skipEnergyPlusPreprocess);
 }
 
 void RunOptions::resetSkipEnergyPlusPreprocess() {
@@ -405,6 +508,10 @@ bool RunOptions::cleanup() const {
   return getImpl<detail::RunOptions_Impl>()->cleanup();
 }
 
+bool RunOptions::isCleanupDefaulted() const {
+  return getImpl<detail::RunOptions_Impl>()->isCleanupDefaulted();
+}
+
 bool RunOptions::setCleanup(bool cleanup) {
   return getImpl<detail::RunOptions_Impl>()->setCleanup(cleanup);
 }
@@ -412,7 +519,6 @@ bool RunOptions::setCleanup(bool cleanup) {
 void RunOptions::resetCleanup() {
   getImpl<detail::RunOptions_Impl>()->resetCleanup();
 }
-
 boost::optional<CustomOutputAdapter> RunOptions::customOutputAdapter() const {
   return getImpl<detail::RunOptions_Impl>()->customOutputAdapter();
 }
@@ -437,6 +543,10 @@ void RunOptions::resetForwardTranslatorOptions() {
   getImpl<detail::RunOptions_Impl>()->resetForwardTranslatorOptions();
 }
 
+void RunOptions::overrideValuesWith(const RunOptions& other) {
+  getImpl<detail::RunOptions_Impl>()->overrideValuesWith(other);
+}
+
 /// @cond
 
 // DEPRECATED
diff --git a/src/utilities/filetypes/RunOptions.hpp b/src/utilities/filetypes/RunOptions.hpp
index d3ff1f9d915..83623e97af4 100644
--- a/src/utilities/filetypes/RunOptions.hpp
+++ b/src/utilities/filetypes/RunOptions.hpp
@@ -63,30 +63,37 @@ class UTILITIES_API RunOptions
   std::string string() const;
 
   bool debug() const;
+  bool isDebugDefaulted() const;
   bool setDebug(bool debug);
   void resetDebug();
 
   bool epjson() const;
+  bool isEpjsonDefaulted() const;
   bool setEpjson(bool epjson);
   void resetEpjson();
 
   bool fast() const;
+  bool isFastDefaulted() const;
   bool setFast(bool fast);
   void resetFast();
 
   bool preserveRunDir() const;
-  bool setPreserveRunDir(bool preserve);
+  bool isPreserveRunDirDefaulted() const;
+  bool setPreserveRunDir(bool preserveRunDir);
   void resetPreserveRunDir();
 
   bool skipExpandObjects() const;
-  bool setSkipExpandObjects(bool skip);
+  bool isSkipExpandObjectsDefaulted() const;
+  bool setSkipExpandObjects(bool skipExpandObjects);
   void resetSkipExpandObjects();
 
   bool skipEnergyPlusPreprocess() const;
-  bool setSkipEnergyPlusPreprocess(bool skip);
+  bool isSkipEnergyPlusPreprocessDefaulted() const;
+  bool setSkipEnergyPlusPreprocess(bool skipEnergyPlusPreprocess);
   void resetSkipEnergyPlusPreprocess();
 
   bool cleanup() const;
+  bool isCleanupDefaulted() const;
   bool setCleanup(bool cleanup);
   void resetCleanup();
 
@@ -102,6 +109,9 @@ class UTILITIES_API RunOptions
   bool setForwardTranslatorOptions(const ForwardTranslatorOptions& forwardTranslatorOptions);
   void resetForwardTranslatorOptions();
 
+  /* Any non-defaulted value from other is brought over */
+  void overrideValuesWith(const RunOptions& other);
+
  protected:
   // get the impl
   template <typename T>
diff --git a/src/utilities/filetypes/RunOptions_Impl.hpp b/src/utilities/filetypes/RunOptions_Impl.hpp
index de29835422a..db961e45999 100644
--- a/src/utilities/filetypes/RunOptions_Impl.hpp
+++ b/src/utilities/filetypes/RunOptions_Impl.hpp
@@ -32,30 +32,37 @@ namespace detail {
     std::string string() const;
 
     bool debug() const;
+    bool isDebugDefaulted() const;
     bool setDebug(bool debug);
     void resetDebug();
 
     bool epjson() const;
+    bool isEpjsonDefaulted() const;
     bool setEpjson(bool epjson);
     void resetEpjson();
 
     bool fast() const;
+    bool isFastDefaulted() const;
     bool setFast(bool fast);
     void resetFast();
 
     bool preserveRunDir() const;
-    bool setPreserveRunDir(bool preserve);
+    bool isPreserveRunDirDefaulted() const;
+    bool setPreserveRunDir(bool preserveRunDir);
     void resetPreserveRunDir();
 
     bool skipExpandObjects() const;
-    bool setSkipExpandObjects(bool skip);
+    bool isSkipExpandObjectsDefaulted() const;
+    bool setSkipExpandObjects(bool skipExpandObjects);
     void resetSkipExpandObjects();
 
     bool skipEnergyPlusPreprocess() const;
-    bool setSkipEnergyPlusPreprocess(bool skip);
+    bool isSkipEnergyPlusPreprocessDefaulted() const;
+    bool setSkipEnergyPlusPreprocess(bool skipEnergyPlusPreprocess);
     void resetSkipEnergyPlusPreprocess();
 
     bool cleanup() const;
+    bool isCleanupDefaulted() const;
     bool setCleanup(bool cleanup);
     void resetCleanup();
 
@@ -67,6 +74,9 @@ namespace detail {
     bool setForwardTranslatorOptions(const ForwardTranslatorOptions& forwardTranslatorOptions);
     void resetForwardTranslatorOptions();
 
+    /* Any non-defaulted value from other is brought over */
+    void overrideValuesWith(const RunOptions& other);
+
     // Emitted on any change
     Nano::Signal<void()> onChange;
 
@@ -77,13 +87,36 @@ namespace detail {
     // configure logging
     REGISTER_LOGGER("openstudio.RunOptions");
 
-    bool m_debug = false;
-    bool m_epjson = false;
-    bool m_fast = false;
-    bool m_preserveRunDir = false;
-    bool m_skipExpandObjects = false;
-    bool m_skipEnergyPlusPreprocess = false;
-    bool m_cleanup = true;  // TODO this does absolutely nothing in the workflow-gem currently
+    static constexpr bool DEFAULT_DEBUG = false;
+    static constexpr bool DEFAULT_EPJSON = false;
+    static constexpr bool DEFAULT_FAST = false;
+    static constexpr bool DEFAULT_PRESERVERUNDIR = false;
+    static constexpr bool DEFAULT_SKIPEXPANDOBJECTS = false;
+    static constexpr bool DEFAULT_SKIPENERGYPLUSPREPROCESS = false;
+    static constexpr bool DEFAULT_CLEANUP = true;
+
+    bool m_debug = DEFAULT_DEBUG;
+    bool m_is_debug_defaulted = true;
+
+    bool m_epjson = DEFAULT_EPJSON;
+    bool m_is_epjson_defaulted = true;
+
+    bool m_fast = DEFAULT_FAST;
+    bool m_is_fast_defaulted = true;
+
+    bool m_preserveRunDir = DEFAULT_PRESERVERUNDIR;
+    bool m_is_preserveRunDir_defaulted = true;
+
+    bool m_skipExpandObjects = DEFAULT_SKIPEXPANDOBJECTS;
+    bool m_is_skipExpandObjects_defaulted = true;
+
+    bool m_skipEnergyPlusPreprocess = DEFAULT_SKIPENERGYPLUSPREPROCESS;
+    bool m_is_skipEnergyPlusPreprocess_defaulted = true;
+
+    // TODO this does absolutely nothing in the workflow-gem currently
+    bool m_cleanup = DEFAULT_CLEANUP;
+    bool m_is_cleanup_defaulted = true;
+
     ForwardTranslatorOptions m_forwardTranslatorOptions;
     boost::optional<CustomOutputAdapter> m_customOutputAdapter;
   };
diff --git a/src/workflow/ApplyMeasure.cpp b/src/workflow/ApplyMeasure.cpp
index c3a07f09ccb..ecce2a7d008 100644
--- a/src/workflow/ApplyMeasure.cpp
+++ b/src/workflow/ApplyMeasure.cpp
@@ -24,12 +24,38 @@
 #include "../utilities/core/Logger.hpp"
 #include "../energyplus/ForwardTranslator.hpp"
 
+#include "../utilities/core/ASCIIStrings.hpp"
 #include "../utilities/filetypes/WorkflowJSON.hpp"
 #include "../utilities/filetypes/RunOptions.hpp"
 #include <boost/filesystem/operations.hpp>
 
+#include <fmt/format.h>
+#include <fmt/ranges.h>
+
 namespace openstudio {
 
+bool isStepMarkedSkip(const std::map<std::string, openstudio::Variant>& stepArgs) {
+  bool skip_measure = false;
+
+  // handle skip first
+  if (!stepArgs.empty() && stepArgs.contains("__SKIP__")) {
+    // TODO: handling of SKIP is incomplete here, will need to increment the runner and co, and not process the measure
+    const openstudio::Variant& argumentValue = stepArgs.at("__SKIP__");
+    VariantType variantType = argumentValue.variantType();
+
+    if (variantType == VariantType::String) {
+      skip_measure = openstudio::ascii_to_lower_copy(argumentValue.valueAsString()) == "true";
+    } else if (variantType == VariantType::Double) {
+      skip_measure = (argumentValue.valueAsDouble() != 0.0);
+    } else if (variantType == VariantType::Integer) {
+      skip_measure = (argumentValue.valueAsInteger() != 0);
+    } else if (variantType == VariantType::Boolean) {
+      skip_measure = argumentValue.valueAsBoolean();
+    }
+  }
+  return skip_measure;
+}
+
 void OSWorkflow::applyMeasures(MeasureType measureType, bool energyplus_output_requests) {
 
   if (m_add_timings && m_detailed_timings) {
@@ -46,10 +72,23 @@ void OSWorkflow::applyMeasures(MeasureType measureType, bool energyplus_output_r
     auto step = stepAndIndex.second;
     LOG(Debug, "Running step " << stepIndex);
 
-    //  measure_run_dir = File.join(run_dir, "#{step_index.to_s.rjust(3,'0')}_#{measure_dir_name}")
-    //  logger.debug "Creating run directory for measure in #{measure_run_dir}"
-    //  FileUtils.mkdir_p measure_run_dir
-    //  Dir.chdir measure_run_dir
+    const auto measureDirName = step.measureDirName();
+
+    auto stepArgs = step.arguments();
+    const bool skip_measure = isStepMarkedSkip(stepArgs);
+    if (skip_measure || runner.halted()) {  // TODO: or halted
+      if (!energyplus_output_requests) {
+        if (runner.halted()) {
+          LOG(Info, fmt::format("Skipping measure '{}' because simulation halted", measureDirName));
+        } else {
+          LOG(Info, fmt::format("Skipping measure '{}'", measureDirName));
+          WorkflowStepResult result = runner.result();
+          runner.incrementStep();
+          result.setStepResult(StepResult::Skip);
+        }
+      }
+      continue;
+    }
 
     if (model.numObjects() > 0) {
       runner.setLastOpenStudioModel(model);
@@ -63,7 +102,6 @@ void OSWorkflow::applyMeasures(MeasureType measureType, bool energyplus_output_r
 
     updateLastWeatherFileFromModel();
 
-    const auto measureDirName = step.measureDirName();
     if (openstudio::filesystem::path(measureDirName).is_absolute()) {
       LOG(Warn, "measure_dir_name should not be a full path. It should be a relative path to the measure directory or the name of the measure "
                 "directory containing the measure.rb / measure.py file.");
@@ -72,11 +110,20 @@ void OSWorkflow::applyMeasures(MeasureType measureType, bool energyplus_output_r
     if (m_add_timings && m_detailed_timings) {
       m_timers->newTimer(fmt::format("Measure::{}", measureDirName), 2);
     }
-
     openstudio::filesystem::path curDirPath = boost::filesystem::current_path();
+
+    auto ensureBlock = [this, &curDirPath](bool stepHasThrown) {
+      if (m_add_timings && m_detailed_timings) {
+        m_timers->tockCurrentTimer();
+        if (stepHasThrown) {
+          m_timers->tockCurrentTimer();
+        }
+      }
+      boost::filesystem::current_path(curDirPath);
+    };
+
     auto thisRunDir = workflowJSON.absoluteRunDir() / openstudio::toPath(fmt::format("{:03}_{}", stepIndex, measureDirName));
     LOG(Debug, "Creating run directory for measure in '" << thisRunDir << "'");
-
     openstudio::filesystem::create_directory(thisRunDir);
     boost::filesystem::current_path(thisRunDir);
 
@@ -103,15 +150,17 @@ void OSWorkflow::applyMeasures(MeasureType measureType, bool energyplus_output_r
     LOG(Debug, "Measure Type: " << bclMeasure.measureType().valueName());
     LOG(Debug, "Measure Language: " << measureLanguage.valueName());
 
-    auto getArguments = [this, &measureType, &scriptPath_, &step](openstudio::measure::OSMeasure* measurePtr) -> measure::OSArgumentMap {
-      if (!measurePtr) {
-        throw std::runtime_error(fmt::format("Could not load measure at '{}'", openstudio::toString(scriptPath_.get())));
-      }
+    auto getArguments = [this, &measureType, &stepArgs](openstudio::measure::OSMeasure* measurePtr) -> measure::OSArgumentMap {
+      LOG(Debug, "measure->name()= '" << measurePtr->name() << "'");
+
+      measure::OSArgumentMap argumentMap;
+
+      //  logger.debug "Iterating over arguments for workflow item '#{measure_dir_name}'"
+      LOG(Debug, "Current step has " << stepArgs.size() << " arguments");
+
       // Initialize arguments which may be model dependent, don't allow arguments method access to real model in case it changes something
       std::vector<measure::OSArgument> arguments;
 
-      LOG(Debug, "measure->name()= '" << measurePtr->name() << "'");
-
       if (measureType == MeasureType::ModelMeasure) {
         // For computing arguments
         auto modelClone = model.clone(true).cast<model::Model>();
@@ -124,29 +173,28 @@ void OSWorkflow::applyMeasures(MeasureType measureType, bool energyplus_output_r
         arguments = static_cast<openstudio::measure::ReportingMeasure*>(measurePtr)->arguments(modelClone);  // NOLINT
       }
 
-      measure::OSArgumentMap argumentMap;
       if (!arguments.empty()) {
         for (const auto& argument : arguments) {
-          LOG(Debug, "Argument: " << argument.name());
+          // LOG(Debug, "Argument: " << argument);
           argumentMap[argument.name()] = argument.clone();
         }
 
-        //  logger.debug "Iterating over arguments for workflow item '#{measure_dir_name}'"
-        auto stepArgs = step.arguments();
-        LOG(Debug, "Current step has " << stepArgs.size() << " arguments");
-        // handle skip first
-        if (!stepArgs.empty() && stepArgs.contains("__SKIP__")) {
-          // TODO: handling of SKIP is incomplete here, will need to increment the runner and co, and not process the measure
-
-        } else {
+        if (!stepArgs.empty()) {
           // TODO: I've copied workflow-gem here, but it's wrong. It's trying to issue a warning if an argument is not set, so it should loop on
           // argumentMap instead...
           for (auto const& [argumentName, argumentValue] : stepArgs) {
+            if (argumentName == "__SKIP__") {
+              continue;
+            }
             applyArguments(argumentMap, argumentName, argumentValue);
           }
         }
       }
 
+      std::map<std::string, std::string> formattedArgMap;
+      std::transform(argumentMap.begin(), argumentMap.end(), std::inserter(formattedArgMap, formattedArgMap.begin()),
+                     [](const auto& p) { return std::make_pair(p.first, p.second.printValue(true)); });
+      LOG(Debug, fmt::format("argumentMap={}", formattedArgMap));
       return argumentMap;
     };
 
@@ -156,70 +204,125 @@ void OSWorkflow::applyMeasures(MeasureType measureType, bool energyplus_output_r
 #if USE_RUBY_ENGINE
       thisEngine = &rubyEngine;
 #else
+      ensureBlock(true);
       throw std::runtime_error("Cannot run a Ruby measure when RubyEngine isn't enabled");
 #endif
     } else if (measureLanguage == MeasureLanguage::Python) {
 #if USE_PYTHON_ENGINE
       thisEngine = &pythonEngine;
 #else
+      ensureBlock(true);
       throw std::runtime_error("Cannot run a Python measure when PythonEngine isn't enabled");
 #endif
     }
 
     ScriptObject measureScriptObject = (*thisEngine)->loadMeasure(*scriptPath_, className);
     if (measureScriptObject.empty()) {
+      ensureBlock(true);
       throw std::runtime_error(fmt::format("Failed to load measure '{}' from '{}'\n", className, openstudio::toString(scriptPath_.get())));
     }
 
     // This pointer will only be valid for as long as the above PythonMeasure is in scope
     // After that, dereferencing the measure pointer will crash the program
     auto* measurePtr = (*thisEngine)->getAs<openstudio::measure::OSMeasure*>(measureScriptObject);
+    if (!measurePtr) {
+      ensureBlock(true);
+      throw std::runtime_error(fmt::format("Could not load measure at '{}'", openstudio::toString(scriptPath_.get())));
+    }
+
+    // Patch measure if needed
+    bool was_patched = false;
+    if (measureType == MeasureType::ReportingMeasure) {
+      const int numArgs = (*thisEngine)->numberOfArguments(measureScriptObject, "arguments");
+      fmt::print("numArgs={}\n", numArgs);
+      if (numArgs == 0) {
+        if (measureLanguage == MeasureLanguage::Ruby) {
+          auto msg = fmt::format("Reporting Measure at {} is using the old format where the 'arguments' method does not take model. "
+                                 " Please consider updating this to `def arguments(model)`.",
+                                 scriptPath_->generic_string());
+          LOG(Warn, msg);
+          auto patchArgumentsCmd = fmt::format(R"ruby(
+module {0}Extensions
+  def arguments(model)
+    super()
+  end
+end
+
+class {0}
+  prepend {0}Extensions # the only change to above: prepend instead of include
+end
+)ruby",
+                                               className);
+          rubyEngine->exec(patchArgumentsCmd);
+          was_patched = true;
+        } else {
+          auto msg = fmt::format("Wrong number of parameters for method `arguments` in reporting_measure '{}' from '{}'\n", className,
+                                 scriptPath_->generic_string());
+          ensureBlock(true);
+          throw std::runtime_error(msg);
+        }
+      }
+    }
 
     const auto argmap = getArguments(measurePtr);
-    // There is a bug. I can run one measure but not two. The one measure can be either python or ruby
-    // I think it might have to do with the operations that must be done to the runner to reset state. maybe?
-    if (measureType == MeasureType::ModelMeasure) {
-      static_cast<openstudio::measure::ModelMeasure*>(measurePtr)->run(model, runner, argmap);
-    } else if (measureType == MeasureType::EnergyPlusMeasure) {
-      static_cast<openstudio::measure::EnergyPlusMeasure*>(measurePtr)->run(workspace_.get(), runner, argmap);
-    } else if (measureType == MeasureType::ReportingMeasure) {
-      if (energyplus_output_requests) {
-        LOG(Debug, "Calling measure.energyPlusOutputRequests for '" << measureDirName << "'");
-
-        std::vector<IdfObject> idfObjects;
-        idfObjects = static_cast<openstudio::measure::ReportingMeasure*>(measurePtr)->energyPlusOutputRequests(runner, argmap);
-
-        int num_added = 0;
-        for (auto& idfObject : idfObjects) {
-          if (openstudio::workflow::util::addEnergyPlusOutputRequest(workspace_.get(), idfObject)) {
-            ++num_added;
+
+    // TODO: this doesn't protect! it'll crash if a wrong method is used in the ruby measure for eg
+    try {
+      if (measureType == MeasureType::ModelMeasure) {
+        static_cast<openstudio::measure::ModelMeasure*>(measurePtr)->run(model, runner, argmap);
+      } else if (measureType == MeasureType::EnergyPlusMeasure) {
+        static_cast<openstudio::measure::EnergyPlusMeasure*>(measurePtr)->run(workspace_.get(), runner, argmap);
+      } else if (measureType == MeasureType::ReportingMeasure) {
+        if (energyplus_output_requests) {
+          LOG(Debug, "Calling measure.energyPlusOutputRequests for '" << measureDirName << "'");
+
+          std::vector<IdfObject> idfObjects;
+          idfObjects = static_cast<openstudio::measure::ReportingMeasure*>(measurePtr)->energyPlusOutputRequests(runner, argmap);
+
+          int num_added = 0;
+          for (auto& idfObject : idfObjects) {
+            if (openstudio::workflow::util::addEnergyPlusOutputRequest(workspace_.get(), idfObject)) {
+              ++num_added;
+            }
           }
+          LOG(Debug, "Finished measure.energyPlusOutputRequests for '" << measureDirName << "', " << num_added << " output requests added");
+        } else {
+          static_cast<openstudio::measure::ReportingMeasure*>(measurePtr)->run(runner, argmap);
         }
-        LOG(Debug, "Finished measure.energyPlusOutputRequests for '" << measureDirName << "', " << num_added << " output requests added");
-      } else {
-        static_cast<openstudio::measure::ReportingMeasure*>(measurePtr)->run(runner, argmap);
       }
+    } catch (const std::exception& e) {
+      runner.registerError(e.what());
+      if (!energyplus_output_requests) {
+        // incrementStep must be called after run
+        runner.incrementStep();
+      }
+      ensureBlock(true);
+      throw std::runtime_error(fmt::format("Runner error: Measure {} reported an error with [{}]", scriptPath_->generic_string(), e.what()));
     }
 
-    WorkflowStepResult result = runner.result();
-    if (auto stepResult_ = result.stepResult()) {
-      LOG(Debug, "Step Result: " << stepResult_->valueName());
-    }
-    // incrementStep must be called after run
-    runner.incrementStep();
-    if (auto errors = result.stepErrors(); !errors.empty()) {
-      throw std::runtime_error(fmt::format("Measure {} reported an error with [{}]\n", measureDirName, fmt::join(errors, "\n")));
-    }
+    // if doing output requests we are done now
+    if (!energyplus_output_requests) {
+      WorkflowStepResult result = runner.result();
+      if (auto stepResult_ = result.stepResult()) {
+        LOG(Debug, "Step Result: " << stepResult_->valueName());
+      }
+      // incrementStep must be called after run
+      runner.incrementStep();
+      if (auto errors = result.stepErrors(); !errors.empty()) {
+        ensureBlock(true);
+        throw std::runtime_error(fmt::format("Measure {} reported an error with [{}]", measureDirName, fmt::join(errors, "\n")));
+      }
 
-    if (measureType == MeasureType::ModelMeasure) {
-      updateLastWeatherFileFromModel();
+      if (measureType == MeasureType::ModelMeasure) {
+        updateLastWeatherFileFromModel();
+      }
     }
 
-    if (m_add_timings && m_detailed_timings) {
-      m_timers->tockCurrentTimer();
+    if (was_patched) {
+      rubyEngine->exec(fmt::format("Object.send(:remove_const, :{}Extensions)", className));
     }
 
-    boost::filesystem::current_path(curDirPath);
+    ensureBlock(false);
 
   }  // End for each step
 
diff --git a/src/workflow/OSWorkflow.cpp b/src/workflow/OSWorkflow.cpp
index 5fbfebde22b..0ed93ab8bed 100644
--- a/src/workflow/OSWorkflow.cpp
+++ b/src/workflow/OSWorkflow.cpp
@@ -87,11 +87,8 @@ OSWorkflow::OSWorkflow(const WorkflowRunOptions& t_workflowRunOptions, ScriptEng
   if (!runOpt_) {
     workflowJSON.setRunOptions(t_workflowRunOptions.runOptions);
   } else {
-    auto ori_ftOptions = runOpt_->forwardTranslatorOptions();
-    workflowJSON.setRunOptions(t_workflowRunOptions.runOptions);
     // user supplied CLI flags trump everything
-    ori_ftOptions.overrideValuesWith(t_workflowRunOptions.runOptions.forwardTranslatorOptions());
-    workflowJSON.runOptions()->setForwardTranslatorOptions(ori_ftOptions);
+    runOpt_->overrideValuesWith(t_workflowRunOptions.runOptions);
   }
 
   if (workflowJSON.runOptions()->debug()) {
@@ -208,7 +205,7 @@ void OSWorkflow::saveIDFToRootDirIfDebug() {
   LOG(Info, "Saved IDF as " << savePath);
 }
 
-void OSWorkflow::run() {
+bool OSWorkflow::run() {
 
   if (!m_show_stdout) {
     openstudio::Logger::instance().standardOutLogger().disable();
@@ -219,7 +216,7 @@ void OSWorkflow::run() {
   // Need to recreate the runDir as fast as possible, so I can direct a file log sink there
   bool hasDeletedRunDir = false;
   auto runDirPath = workflowJSON.absoluteRunDir();
-  if (!workflowJSON.runOptions()->preserveRunDir()) {
+  if (!workflowJSON.runOptions()->preserveRunDir() && !m_post_process_only) {
     // We don't have a run_dir argument anyways
     if (openstudio::filesystem::is_directory(runDirPath)) {
       hasDeletedRunDir = true;
@@ -362,22 +359,40 @@ void OSWorkflow::run() {
     jobMap.at("Cleanup").selected = false;
   }
 
+  std::string lastFatalError;
+
   for (auto& [jobName, jobInfo] : jobMap) {
     LOG(Debug, fmt::format("{} - selected = {}\n", jobName, jobInfo.selected));
     if (jobInfo.selected) {
-      timeJob(jobInfo.jobFun, std::string{jobName});
+      try {
+        timeJob(jobInfo.jobFun, std::string{jobName});
+      } catch (std::exception& e) {
+        if (m_add_timings) {
+          m_timers->tockCurrentTimer();
+        }
+        lastFatalError = fmt::format("Found error in state '{}' with message :'{}'", jobName, e.what());
+        LOG(Error, lastFatalError);
+        // Allow continuing anyways if it fails in reporting measures
+        if (jobName != "ReportingMeasures") {
+          state = State::Errored;
+          break;
+        }
+      }
     } else {
       LOG(Info, "Skipping job " << jobName);
     }
   }
 
-  // Save final IDF
-  if (m_add_timings) {
-    m_timers->newTimer("Save IDF");
-  }
-  workspace_->save(runDirPath / "in.idf", true);  // TODO: Is this really necessary? Seems like it's done before already
-  if (m_add_timings) {
-    m_timers->tockCurrentTimer();
+  // TODO: Is this really necessary? Seems like it's done before already (in RunPreProcess)
+  if (workspace_) {
+    // Save final IDF
+    if (m_add_timings) {
+      m_timers->newTimer("Save IDF");
+    }
+    workspace_->save(runDirPath / "in.idf", true);
+    if (m_add_timings) {
+      m_timers->tockCurrentTimer();
+    }
   }
 
   if (!workflowJSON.runOptions()->fast()) {
@@ -427,12 +442,17 @@ void OSWorkflow::run() {
     OS_ASSERT(file.is_open());
     file << fmt::format("Finished Workflow {}\n", std::chrono::system_clock::now());
     file.close();
+    state = State::Finished;
   }
 
   if (m_add_timings) {
     fmt::print("\nTiming:\n\n{}\n", m_timers->timeReport());
-
     // TODO: create profile.json in the run folder
   }
+
+  if (!lastFatalError.empty()) {
+    fmt::print(stderr, "Failed to run workflow\n  Last Error:\n{}\n", lastFatalError);
+  }
+  return (state == State::Finished);
 }
 }  // namespace openstudio
diff --git a/src/workflow/OSWorkflow.hpp b/src/workflow/OSWorkflow.hpp
index e7b608f479f..e3ec4d86474 100644
--- a/src/workflow/OSWorkflow.hpp
+++ b/src/workflow/OSWorkflow.hpp
@@ -36,7 +36,7 @@ class OSWorkflow
   OSWorkflow(const filesystem::path& oswPath, ScriptEngineInstance& ruby, ScriptEngineInstance& python);
   OSWorkflow(const WorkflowRunOptions& t_workflowRunOptions, ScriptEngineInstance& ruby, ScriptEngineInstance& python);
 
-  void run();
+  bool run();
 
  private:
   REGISTER_LOGGER("openstudio.workflow.OSWorkflow");
diff --git a/src/workflow/Timer.cpp b/src/workflow/Timer.cpp
index d22918e9687..b9228b935d3 100644
--- a/src/workflow/Timer.cpp
+++ b/src/workflow/Timer.cpp
@@ -41,7 +41,9 @@ std::string Timer::message() const {
 }
 
 auto Timer::duration() const {
-  OS_ASSERT(m_end != TimePointType{});
+  if (m_end == TimePointType{}) {
+    throw std::runtime_error(fmt::format("Timer {} with level={} was not stopped", m_message, m_level));
+  }
   return std::chrono::duration_cast<DurationType>(m_end - m_start);
 }
 


Test patch:
diff --git a/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp b/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp
index 18a17a877b1..5028f4bd578 100644
--- a/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp
+++ b/src/utilities/filetypes/test/WorkflowJSON_GTest.cpp
@@ -1154,3 +1154,268 @@ TEST(Filetypes, RunOptions_NoFtOptions) {
   const RunOptions options;
   EXPECT_EQ("", options.forwardTranslateOptions());
 }
+
+TEST(Filetypes, RunOptions_GettersSetters) {
+  RunOptions runOptions;
+
+  // Ctor Default
+  ASSERT_FALSE(runOptions.debug());
+  ASSERT_TRUE(runOptions.isDebugDefaulted());
+  // Set to opposite of default
+  ASSERT_TRUE(runOptions.setDebug(true));
+  ASSERT_TRUE(runOptions.debug());
+  ASSERT_FALSE(runOptions.isDebugDefaulted());
+  // Reset
+  runOptions.resetDebug();
+  ASSERT_FALSE(runOptions.debug());
+  ASSERT_TRUE(runOptions.isDebugDefaulted());
+  ASSERT_TRUE(runOptions.isDebugDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(runOptions.epjson());
+  ASSERT_TRUE(runOptions.isEpjsonDefaulted());
+  // Set to opposite of default
+  ASSERT_TRUE(runOptions.setEpjson(true));
+  ASSERT_TRUE(runOptions.epjson());
+  ASSERT_FALSE(runOptions.isEpjsonDefaulted());
+  // Reset
+  runOptions.resetEpjson();
+  ASSERT_FALSE(runOptions.epjson());
+  ASSERT_TRUE(runOptions.isEpjsonDefaulted());
+  ASSERT_TRUE(runOptions.isEpjsonDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(runOptions.fast());
+  ASSERT_TRUE(runOptions.isFastDefaulted());
+  // Set to opposite of default
+  ASSERT_TRUE(runOptions.setFast(true));
+  ASSERT_TRUE(runOptions.fast());
+  ASSERT_FALSE(runOptions.isFastDefaulted());
+  // Reset
+  runOptions.resetFast();
+  ASSERT_FALSE(runOptions.fast());
+  ASSERT_TRUE(runOptions.isFastDefaulted());
+  ASSERT_TRUE(runOptions.isFastDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(runOptions.preserveRunDir());
+  ASSERT_TRUE(runOptions.isPreserveRunDirDefaulted());
+  // Set to opposite of default
+  ASSERT_TRUE(runOptions.setPreserveRunDir(true));
+  ASSERT_TRUE(runOptions.preserveRunDir());
+  ASSERT_FALSE(runOptions.isPreserveRunDirDefaulted());
+  // Reset
+  runOptions.resetPreserveRunDir();
+  ASSERT_FALSE(runOptions.preserveRunDir());
+  ASSERT_TRUE(runOptions.isPreserveRunDirDefaulted());
+  ASSERT_TRUE(runOptions.isPreserveRunDirDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(runOptions.skipExpandObjects());
+  ASSERT_TRUE(runOptions.isSkipExpandObjectsDefaulted());
+  // Set to opposite of default
+  ASSERT_TRUE(runOptions.setSkipExpandObjects(true));
+  ASSERT_TRUE(runOptions.skipExpandObjects());
+  ASSERT_FALSE(runOptions.isSkipExpandObjectsDefaulted());
+  // Reset
+  runOptions.resetSkipExpandObjects();
+  ASSERT_FALSE(runOptions.skipExpandObjects());
+  ASSERT_TRUE(runOptions.isSkipExpandObjectsDefaulted());
+  ASSERT_TRUE(runOptions.isSkipExpandObjectsDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(runOptions.skipEnergyPlusPreprocess());
+  ASSERT_TRUE(runOptions.isSkipEnergyPlusPreprocessDefaulted());
+  // Set to opposite of default
+  ASSERT_TRUE(runOptions.setSkipEnergyPlusPreprocess(true));
+  ASSERT_TRUE(runOptions.skipEnergyPlusPreprocess());
+  ASSERT_FALSE(runOptions.isSkipEnergyPlusPreprocessDefaulted());
+  // Reset
+  runOptions.resetSkipEnergyPlusPreprocess();
+  ASSERT_FALSE(runOptions.skipEnergyPlusPreprocess());
+  ASSERT_TRUE(runOptions.isSkipEnergyPlusPreprocessDefaulted());
+  ASSERT_TRUE(runOptions.isSkipEnergyPlusPreprocessDefaulted());
+
+  // Ctor Default
+  ASSERT_TRUE(runOptions.cleanup());
+  ASSERT_TRUE(runOptions.isCleanupDefaulted());
+  // Set to opposite of default
+  ASSERT_TRUE(runOptions.setCleanup(false));
+  ASSERT_FALSE(runOptions.cleanup());
+  ASSERT_FALSE(runOptions.isCleanupDefaulted());
+  // Reset
+  runOptions.resetCleanup();
+  ASSERT_TRUE(runOptions.cleanup());
+  ASSERT_TRUE(runOptions.isCleanupDefaulted());
+  ASSERT_TRUE(runOptions.isCleanupDefaulted());
+}
+TEST(Filetypes, ForwardTranslatorOptions_GettersSetters) {
+
+  ForwardTranslatorOptions ftOptions;
+
+  // Ctor Default
+  ASSERT_TRUE(ftOptions.keepRunControlSpecialDays());
+  ASSERT_TRUE(ftOptions.isKeepRunControlSpecialDaysDefaulted());
+  // Set to opposite of default
+  ftOptions.setKeepRunControlSpecialDays(false);
+  ASSERT_FALSE(ftOptions.keepRunControlSpecialDays());
+  ASSERT_FALSE(ftOptions.isKeepRunControlSpecialDaysDefaulted());
+  // Reset
+  ftOptions.resetKeepRunControlSpecialDays();
+  ASSERT_TRUE(ftOptions.keepRunControlSpecialDays());
+  ASSERT_TRUE(ftOptions.isKeepRunControlSpecialDaysDefaulted());
+  ASSERT_TRUE(ftOptions.isKeepRunControlSpecialDaysDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(ftOptions.iPTabularOutput());
+  ASSERT_TRUE(ftOptions.isIPTabularOutputDefaulted());
+  // Set to opposite of default
+  ftOptions.setIPTabularOutput(true);
+  ASSERT_TRUE(ftOptions.iPTabularOutput());
+  ASSERT_FALSE(ftOptions.isIPTabularOutputDefaulted());
+  // Reset
+  ftOptions.resetIPTabularOutput();
+  ASSERT_FALSE(ftOptions.iPTabularOutput());
+  ASSERT_TRUE(ftOptions.isIPTabularOutputDefaulted());
+  ASSERT_TRUE(ftOptions.isIPTabularOutputDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(ftOptions.excludeLCCObjects());
+  ASSERT_TRUE(ftOptions.isExcludeLCCObjectsDefaulted());
+  // Set to opposite of default
+  ftOptions.setExcludeLCCObjects(true);
+  ASSERT_TRUE(ftOptions.excludeLCCObjects());
+  ASSERT_FALSE(ftOptions.isExcludeLCCObjectsDefaulted());
+  // Reset
+  ftOptions.resetExcludeLCCObjects();
+  ASSERT_FALSE(ftOptions.excludeLCCObjects());
+  ASSERT_TRUE(ftOptions.isExcludeLCCObjectsDefaulted());
+  ASSERT_TRUE(ftOptions.isExcludeLCCObjectsDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(ftOptions.excludeSQliteOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeSQliteOutputReportDefaulted());
+  // Set to opposite of default
+  ftOptions.setExcludeSQliteOutputReport(true);
+  ASSERT_TRUE(ftOptions.excludeSQliteOutputReport());
+  ASSERT_FALSE(ftOptions.isExcludeSQliteOutputReportDefaulted());
+  // Reset
+  ftOptions.resetExcludeSQliteOutputReport();
+  ASSERT_FALSE(ftOptions.excludeSQliteOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeSQliteOutputReportDefaulted());
+  ASSERT_TRUE(ftOptions.isExcludeSQliteOutputReportDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(ftOptions.excludeHTMLOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeHTMLOutputReportDefaulted());
+  // Set to opposite of default
+  ftOptions.setExcludeHTMLOutputReport(true);
+  ASSERT_TRUE(ftOptions.excludeHTMLOutputReport());
+  ASSERT_FALSE(ftOptions.isExcludeHTMLOutputReportDefaulted());
+  // Reset
+  ftOptions.resetExcludeHTMLOutputReport();
+  ASSERT_FALSE(ftOptions.excludeHTMLOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeHTMLOutputReportDefaulted());
+  ASSERT_TRUE(ftOptions.isExcludeHTMLOutputReportDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(ftOptions.excludeVariableDictionary());
+  ASSERT_TRUE(ftOptions.isExcludeVariableDictionaryDefaulted());
+  // Set to opposite of default
+  ftOptions.setExcludeVariableDictionary(true);
+  ASSERT_TRUE(ftOptions.excludeVariableDictionary());
+  ASSERT_FALSE(ftOptions.isExcludeVariableDictionaryDefaulted());
+  // Reset
+  ftOptions.resetExcludeVariableDictionary();
+  ASSERT_FALSE(ftOptions.excludeVariableDictionary());
+  ASSERT_TRUE(ftOptions.isExcludeVariableDictionaryDefaulted());
+  ASSERT_TRUE(ftOptions.isExcludeVariableDictionaryDefaulted());
+
+  // Ctor Default
+  ASSERT_FALSE(ftOptions.excludeSpaceTranslation());
+  ASSERT_TRUE(ftOptions.isExcludeSpaceTranslationDefaulted());
+  // Set to opposite of default
+  ftOptions.setExcludeSpaceTranslation(true);
+  ASSERT_TRUE(ftOptions.excludeSpaceTranslation());
+  ASSERT_FALSE(ftOptions.isExcludeSpaceTranslationDefaulted());
+  // Reset
+  ftOptions.resetExcludeSpaceTranslation();
+  ASSERT_FALSE(ftOptions.excludeSpaceTranslation());
+  ASSERT_TRUE(ftOptions.isExcludeSpaceTranslationDefaulted());
+  ASSERT_TRUE(ftOptions.isExcludeSpaceTranslationDefaulted());
+}
+
+TEST(Filetypes, RunOptions_overrideValuesWith) {
+
+  RunOptions runOptions;
+  ForwardTranslatorOptions ftOptions = runOptions.forwardTranslatorOptions();
+
+  ASSERT_FALSE(runOptions.debug());
+  ASSERT_TRUE(runOptions.isDebugDefaulted());
+  ASSERT_FALSE(runOptions.epjson());
+  ASSERT_TRUE(runOptions.isEpjsonDefaulted());
+  ASSERT_FALSE(runOptions.fast());
+  ASSERT_TRUE(runOptions.isFastDefaulted());
+  ASSERT_FALSE(runOptions.preserveRunDir());
+  ASSERT_TRUE(runOptions.isPreserveRunDirDefaulted());
+  ASSERT_FALSE(runOptions.skipExpandObjects());
+  ASSERT_TRUE(runOptions.isSkipExpandObjectsDefaulted());
+  ASSERT_FALSE(runOptions.skipEnergyPlusPreprocess());
+  ASSERT_TRUE(runOptions.isSkipEnergyPlusPreprocessDefaulted());
+  ASSERT_TRUE(runOptions.cleanup());
+  ASSERT_TRUE(runOptions.isCleanupDefaulted());
+
+  ASSERT_TRUE(ftOptions.keepRunControlSpecialDays());
+  ASSERT_TRUE(ftOptions.isKeepRunControlSpecialDaysDefaulted());
+  ASSERT_FALSE(ftOptions.iPTabularOutput());
+  ASSERT_TRUE(ftOptions.isIPTabularOutputDefaulted());
+  ASSERT_FALSE(ftOptions.excludeLCCObjects());
+  ASSERT_TRUE(ftOptions.isExcludeLCCObjectsDefaulted());
+  ASSERT_FALSE(ftOptions.excludeSQliteOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeSQliteOutputReportDefaulted());
+  ASSERT_FALSE(ftOptions.excludeHTMLOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeHTMLOutputReportDefaulted());
+  ASSERT_FALSE(ftOptions.excludeVariableDictionary());
+  ASSERT_TRUE(ftOptions.isExcludeVariableDictionaryDefaulted());
+  ASSERT_FALSE(ftOptions.excludeSpaceTranslation());
+  ASSERT_TRUE(ftOptions.isExcludeSpaceTranslationDefaulted());
+
+  RunOptions otherRunOptions;
+  ForwardTranslatorOptions otherftOptions = otherRunOptions.forwardTranslatorOptions();
+
+  otherRunOptions.setDebug(false);                    // Explicitly set to default
+  otherRunOptions.setEpjson(true);                    // Explicitly set to opposite of default
+  otherftOptions.setKeepRunControlSpecialDays(true);  // Explicitly set to default
+  otherftOptions.setIPTabularOutput(true);            // Explicitly set to opposite of default
+
+  runOptions.overrideValuesWith(otherRunOptions);
+  ASSERT_FALSE(runOptions.debug());
+  ASSERT_FALSE(runOptions.isDebugDefaulted());  // No longer defaulted
+  ASSERT_TRUE(runOptions.epjson());
+  ASSERT_FALSE(runOptions.isEpjsonDefaulted());  // No longer defaulted
+  ASSERT_FALSE(runOptions.fast());
+  ASSERT_TRUE(runOptions.isFastDefaulted());
+  ASSERT_FALSE(runOptions.preserveRunDir());
+  ASSERT_TRUE(runOptions.isPreserveRunDirDefaulted());
+  ASSERT_FALSE(runOptions.skipExpandObjects());
+  ASSERT_TRUE(runOptions.isSkipExpandObjectsDefaulted());
+  ASSERT_FALSE(runOptions.skipEnergyPlusPreprocess());
+  ASSERT_TRUE(runOptions.isSkipEnergyPlusPreprocessDefaulted());
+  ASSERT_TRUE(runOptions.cleanup());
+  ASSERT_TRUE(runOptions.isCleanupDefaulted());
+
+  ASSERT_TRUE(ftOptions.keepRunControlSpecialDays());
+  ASSERT_FALSE(ftOptions.isKeepRunControlSpecialDaysDefaulted());  // No longer defaulted
+  ASSERT_TRUE(ftOptions.iPTabularOutput());
+  ASSERT_FALSE(ftOptions.isIPTabularOutputDefaulted());  // No longer defaulted
+  ASSERT_FALSE(ftOptions.excludeLCCObjects());
+  ASSERT_TRUE(ftOptions.isExcludeLCCObjectsDefaulted());
+  ASSERT_FALSE(ftOptions.excludeSQliteOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeSQliteOutputReportDefaulted());
+  ASSERT_FALSE(ftOptions.excludeHTMLOutputReport());
+  ASSERT_TRUE(ftOptions.isExcludeHTMLOutputReportDefaulted());
+  ASSERT_FALSE(ftOptions.excludeVariableDictionary());
+  ASSERT_TRUE(ftOptions.isExcludeVariableDictionaryDefaulted());
+  ASSERT_FALSE(ftOptions.excludeSpaceTranslation());
+  ASSERT_TRUE(ftOptions.isExcludeSpaceTranslationDefaulted());
+}