Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #5233 - Validate OSW measures before running #5295

Merged
merged 7 commits into from
Nov 11, 2024
Merged

Conversation

jmarrec
Copy link
Collaborator

@jmarrec jmarrec commented Nov 7, 2024

Pull request overview

Pull Request Author

  • Model API Changes / Additions
  • Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
  • Model API methods are tested (in src/model/test)
  • EnergyPlus ForwardTranslator Tests (in src/energyplus/Test)
  • If a new object or method, added a test in NREL/OpenStudio-resources: Add Link
  • If needed, added VersionTranslation rules for the objects (src/osversion/VersionTranslator.cpp)
  • Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
  • All new and existing tests passes
  • If methods have been deprecated, update rest of code to use the new methods

Labels:

  • If change to an IDD file, add the label IDDChange
  • If breaking existing API, add the label APIChange
  • If deemed ready, add label Pull Request - Ready for CI so that CI builds your PR

Review Checklist

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • Code Style, strip trailing whitespace, etc.
  • All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
  • Labeling is ok
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified

@jmarrec jmarrec added component - Workflow Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge. labels Nov 7, 2024
@jmarrec jmarrec self-assigned this Nov 7, 2024
Comment on lines +11 to +12
"measure_dir_name": "NON_EXISTING_MEASURE_THIS_SHOULD_BE_CAUGHT",
"arguments": {}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test 1

Comment on lines +11 to +12
"measure_dir_name": "UnloadableMeasure",
"arguments": {}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an existing directory (findMeasure works), but it doesn't have a measure.xml

Comment on lines +6 to +13
{
"measure_dir_name": "FakeReport",
"arguments": {}
},
{
"measure_dir_name": "FakeModelMeasure",
"arguments": {}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong order

Comment on lines 273 to 328
# ======================== Workflows should fail ========================
add_test(NAME OpenStudioCLI.Run_Validate.MissingAMeasure
COMMAND $<TARGET_FILE:openstudio> run --show-stdout -w missing_a_measure.osw
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/workflow/invalid_measures/"
)
set_tests_properties(OpenStudioCLI.Run_Validate.MissingAMeasure PROPERTIES
WILL_FAIL TRUE
RESOURCE_LOCK "invalid_measures"
)

add_test(NAME OpenStudioCLI.Run_Validate.UnloadableMeasure
COMMAND $<TARGET_FILE:openstudio> run --show-stdout -w unloadable_measure.osw
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/workflow/invalid_measures/"
)
set_tests_properties(OpenStudioCLI.Run_Validate.UnloadableMeasure PROPERTIES
WILL_FAIL TRUE
RESOURCE_LOCK "invalid_measures"
)

add_test(NAME OpenStudioCLI.Run_Validate.WrongMeasureTypeOrder
COMMAND $<TARGET_FILE:openstudio> run --show-stdout -w wrong_measure_type_order.osw
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/workflow/invalid_measures/"
)
set_tests_properties(OpenStudioCLI.Run_Validate.WrongMeasureTypeOrder PROPERTIES
WILL_FAIL TRUE
RESOURCE_LOCK "invalid_measures"
)

# Classic
add_test(NAME OpenStudioCLI.Classic.Run_Validate.MissingAMeasure
COMMAND $<TARGET_FILE:openstudio> classic run --show-stdout -w missing_a_measure.osw
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/workflow/invalid_measures/"
)
set_tests_properties(OpenStudioCLI.Classic.Run_Validate.MissingAMeasure PROPERTIES
WILL_FAIL TRUE
RESOURCE_LOCK "invalid_measures"
)

add_test(NAME OpenStudioCLI.Classic.Run_Validate.UnloadableMeasure
COMMAND $<TARGET_FILE:openstudio> run --show-stdout -w unloadable_measure.osw
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/workflow/invalid_measures/"
)
set_tests_properties(OpenStudioCLI.Classic.Run_Validate.UnloadableMeasure PROPERTIES
WILL_FAIL TRUE
RESOURCE_LOCK "invalid_measures"
)

add_test(NAME OpenStudioCLI.Classic.Run_Validate.WrongMeasureTypeOrder
COMMAND $<TARGET_FILE:openstudio> run --show-stdout -w wrong_measure_type_order.osw
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/resources/workflow/invalid_measures/"
)
set_tests_properties(OpenStudioCLI.Classic.Run_Validate.WrongMeasureTypeOrder PROPERTIES
WILL_FAIL TRUE
RESOURCE_LOCK "invalid_measures"
)
# ====================== End Workflows should fail ======================
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a c++ and a classic (ruby) version of the calling the CLI on these broken workflows. Expect all of them to fail

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intently pushed just these tests for now, so that CI would report a test failure and highlight the fact that these correctly test the issue #5233

       3798 - OpenStudioCLI.Run_Validate.MissingAMeasure (Failed)
	3799 - OpenStudioCLI.Run_Validate.UnloadableMeasure (Failed)
	3800 - OpenStudioCLI.Run_Validate.WrongMeasureTypeOrder (Failed)

Comment on lines +236 to +237
/** Checks that all measures in the Workflow can be found, and are in the correct order (ModelMeasure > EnergyPlusMeasure > ReportingMeasure) */
bool validateMeasures() const;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new WorkflowJSON method. Non-throwy, logs Errors and return a bool

Comment on lines +71 to +73
if (!p.empty()) {
setOswPath(p, false);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the std::string& constructor, if it happens to be a path, do call setOswPath like we do for the ctor that takes a openstudio::path.

In Ruby, that means on develop you have a different behavior between these two!

OpenStudio::WorkflowJSON.new("/path/to/workflow.osw")
OpenStudio::WorkflowJSON.new(OpenStudio::toPath("/path/to/workflow.osw"))

I noticed this because if the oswPath isn't set, the setMeasuresTypes (private, impl only) isn't called, and if you try to call workflow.getMeasureStepsWithIndex or getMeasureSteps you hit an OS_ASSERT

OS_ASSERT(m_measureTypes.size() == n);

I think that's pretty broken, but it's outside of the scope of this PR to fix it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we document this to fix later that's good

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it might seem a bit backwards, but perhaps the openstudio::path version of the constructor should just forward the call to the string version.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is there seems to be an underiable opportunity to have different behaviors between the two constructors, since the path version has its own implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh. You can guess why it's the way it is.

  • The string constructor was really only meant to take an actual JSON string in originally
  • The path& one was to read from a file on disk

People using ruby especially would try to do OpenStudio::WorkflowJSON.new("/my/workflow.osw") (instead of OpenStudio::WorkflowJSON.new(OpenStudio::toPath("/my/workflow.osw")) and would be very confused, so instead the string version when it doesn't parse correctly, it falls back to check if the string argument is actually a path, and if so I'm pretty sure the intent was to do exactly the same thing. C++ constructor chaining is not possible, so instead of bothering to make an internal method fromPath(...) for a couple of lines, the path version was copy pasted in. This happened in a7efe66 , and probably just forgot the setOswPath since the flow control (with parseSteps etc) was different.

Comment on lines +860 to +861
bool WorkflowJSON_Impl::validateMeasures() const {
// TODO: should we exit early, or return all problems found?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DavidGoldwasser @kbenne thoughts here? I went with the "Find all problems" approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the find all problems approach and I think that is what most people will want.

Outside of the scope here, but I'm thinking that perhaps "validation" would be stronger if we actually loaded the measure script into the Python/Ruby engine. Unless I'm mistaken, as it stands, I'm pretty sure that is not happening here. We have encountered some issues (like this) where things go sideways, yet the root cause is that the measure syntax is flawed, but the failure mode misleads the user to thinking there might be an issue within OpenStudio itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale of catching this early is perhaps sound, but it's also slow. Not sure what's better.

Comment on lines +863 to +882
bool result = true;
MeasureType state = MeasureType::ModelMeasure;

for (size_t i = 0; const auto& step : m_steps) {
LOG(Debug, "Validating step " << i);
if (auto step_ = step.optionalCast<MeasureStep>()) {
// Not calling getBCLMeasure because I want to mimic workflow-gem and be as explicit as possible about what went wrong
const auto measureDirName = step_->measureDirName();
auto measurePath_ = findMeasure(measureDirName);
if (!measurePath_) {
LOG(Error, "Cannot find measure '" << measureDirName << "'");
result = false;
continue;
}
auto bclMeasure_ = BCLMeasure::load(*measurePath_);
if (!bclMeasure_) {
LOG(Error, "Cannot load measure '" << measureDirName << "' at '" << *measurePath_ << "'");
result = false;
continue;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For each step, check we can find the measure directory, and it's a valid BCL Measure.

Comment on lines +884 to +914
// Ensure that measures are in order, i.e. no OS after E+, E+ or OS after Reporting
const auto measureType = bclMeasure_->measureType();

if (measureType == MeasureType::ModelMeasure) {
if (state == MeasureType::EnergyPlusMeasure) {
LOG(Error, "OpenStudio measure '" << measureDirName << "' called after transition to EnergyPlus.");
result = false;
}
if (state == MeasureType::ReportingMeasure) {
LOG(Error, "OpenStudio measure '" << measureDirName << "' called after Energyplus simulation.");
result = false;
}

} else if (measureType == MeasureType::EnergyPlusMeasure) {
if (state == MeasureType::ReportingMeasure) {
LOG(Error, "EnergyPlus measure '" << measureDirName << "' called after Energyplus simulation.");
result = false;
}
if (state == MeasureType::ModelMeasure) {
state = MeasureType::EnergyPlusMeasure;
}

} else if (measureType == MeasureType::ReportingMeasure) {
state = MeasureType::ReportingMeasure;

} else {
LOG(Error, "MeasureType " << measureType.valueName() << " of measure '" << measureDirName << "' is not supported");
result = false;
}
}
++i;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And ensure the measures are in the right order

Comment on lines 57 to +67
// TODO: Validate the OSW measures if the flag is set to true, (the default state)
// Note JM 2022-11-07: Is it better to try and load all measures once, instead of crashing later?
// There isn't a 'verify_osw' key in the RunOptions, so always do it for now. Maybe don't if `fast`?
{
LOG(Info, "Attempting to validate the measure workflow");

if (!workflowJSON.validateMeasures()) {
LOG_AND_THROW("Workflow is invalid");
}

LOG(Info, "Validated the measure workflow");
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as https://github.com/NREL/OpenStudio-workflow-gem/blob/39c84e45f152446a92379a501807e2691dba15ab/lib/openstudio/workflow/jobs/run_initialization.rb#L89-L94

TODO:

  • Should verify_osw be added to the OSW Schema and to RunOptions?
  • Should we maybe not validate it if fast is passed? @shorowit might have opinions on this

Comment on lines 1442 to 1485
TEST(Filetypes, WorkflowJSON_ValidateMeasures_Ok) {
auto p = resourcesPath() / toPath("utilities/Filetypes/full.osw");
WorkflowJSON w(p);
EXPECT_TRUE(w.validateMeasures());
}

TEST(Filetypes, WorkflowJSON_ValidateMeasures_Missing) {
auto p = resourcesPath() / toPath("workflow/invalid_measures/missing_a_measure.osw");
ASSERT_TRUE(boost::filesystem::is_regular_file(p));
WorkflowJSON w(p);
StringStreamLogSink sink;
sink.setLogLevel(Error);
EXPECT_FALSE(w.validateMeasures());
ASSERT_EQ(1, sink.logMessages().size());
EXPECT_EQ("Cannot find measure 'NON_EXISTING_MEASURE_THIS_SHOULD_BE_CAUGHT'", sink.logMessages()[0].logMessage());
}

TEST(Filetypes, WorkflowJSON_ValidateMeasures_Unloadable) {
auto p = resourcesPath() / toPath("workflow/invalid_measures/unloadable_measure.osw");
ASSERT_TRUE(boost::filesystem::is_regular_file(p));
WorkflowJSON w(p);
StringStreamLogSink sink;
sink.setLogLevel(Error);
EXPECT_FALSE(w.validateMeasures());
auto logMessages = sink.logMessages();
ASSERT_EQ(3, logMessages.size());
EXPECT_EQ("utilities.bcl.BCLXML", logMessages.at(0).logChannel());
EXPECT_EQ("utilities.bcl.BCLMeasure", logMessages.at(1).logChannel());
EXPECT_EQ("openstudio.WorkflowJSON", logMessages.at(2).logChannel());
auto logMessage = sink.logMessages()[2].logMessage();
EXPECT_TRUE(logMessage.find("Cannot load measure 'UnloadableMeasure' at '") != std::string::npos) << logMessage;
}

TEST(Filetypes, WorkflowJSON_ValidateMeasures_WrongOrder) {
auto p = resourcesPath() / toPath("workflow/invalid_measures/wrong_measure_type_order.osw");
ASSERT_TRUE(boost::filesystem::is_regular_file(p));
WorkflowJSON w(p);
StringStreamLogSink sink;
sink.setLogLevel(Error);
EXPECT_FALSE(w.validateMeasures());
ASSERT_EQ(1, sink.logMessages().size());

EXPECT_EQ("OpenStudio measure 'ModelMeasureRegistersError' called after Energyplus simulation.", sink.logMessages()[0].logMessage());
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gtests also added

* One references a non existing measure
* One references a folder that can be found but isn't a valid BCLMeasure
* One has a ReportingMeasure before a ModelMeasure
… DO run with current C++ CLI to show problem)
…rror messages

TODO: should we return early?
Forgot to update the Ctest after using another measure
@DavidGoldwasser DavidGoldwasser added this to the OpenStudio SDK 3.9.0 milestone Nov 8, 2024
Comment on lines +71 to +73
if (!p.empty()) {
setOswPath(p, false);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it might seem a bit backwards, but perhaps the openstudio::path version of the constructor should just forward the call to the string version.

Comment on lines +71 to +73
if (!p.empty()) {
setOswPath(p, false);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is there seems to be an underiable opportunity to have different behaviors between the two constructors, since the path version has its own implementation.

Comment on lines +860 to +861
bool WorkflowJSON_Impl::validateMeasures() const {
// TODO: should we exit early, or return all problems found?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the find all problems approach and I think that is what most people will want.

Outside of the scope here, but I'm thinking that perhaps "validation" would be stronger if we actually loaded the measure script into the Python/Ruby engine. Unless I'm mistaken, as it stands, I'm pretty sure that is not happening here. We have encountered some issues (like this) where things go sideways, yet the root cause is that the measure syntax is flawed, but the failure mode misleads the user to thinking there might be an issue within OpenStudio itself.

@kbenne
Copy link
Contributor

kbenne commented Nov 11, 2024

I'm going to go ahead merge, but I left a few discussion points.

@kbenne kbenne merged commit 8cccf80 into develop Nov 11, 2024
5 of 6 checks passed
@kbenne kbenne deleted the 5233_ValidateOSW branch November 11, 2024 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component - Workflow Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

running osw when measure not found, instead of stopping it keeps running the osw and E+.
4 participants