diff --git a/export/tests/CMakeLists.txt b/export/tests/CMakeLists.txt index 8b1347e..e188d08 100644 --- a/export/tests/CMakeLists.txt +++ b/export/tests/CMakeLists.txt @@ -1,22 +1,42 @@ -function(make_test modelIdentifier sources) - add_executable(${modelIdentifier} ${sources} "$" "$") - add_test(NAME ${modelIdentifier} COMMAND ${modelIdentifier}) - target_link_libraries(${modelIdentifier} PUBLIC Catch2::Catch2WithMain) - target_compile_definitions(${modelIdentifier} +function(make_test fmi_version modelIdentifier sources) + set(target_name "${modelIdentifier}_${fmi_version}") + add_executable(${target_name} ${sources} "$" "$") + add_test(NAME ${target_name} COMMAND ${target_name}) + target_link_libraries(${target_name} PUBLIC Catch2::Catch2WithMain) + target_compile_definitions(${target_name} PRIVATE FMU4CPP_MODEL_IDENTIFIER="${modelIdentifier}" - TEST_CASE_RESOURCE_LOCATION="${CMAKE_CURRENT_SOURCE_DIR}/resources" + TEST_CASE_RESOURCE_LOCATION="${PROJECT_SOURCE_DIR}/export/tests/resources" ) - target_include_directories(${modelIdentifier} + target_include_directories(${target_name} PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../include" - "${CMAKE_CURRENT_SOURCE_DIR}/../src" + "${PROJECT_SOURCE_DIR}/export/include" + "${PROJECT_SOURCE_DIR}/export/src" ) endfunction() -make_test(array_test array_test.cpp) -make_test(basic_test basic_test.cpp) -make_test(identity_test identity_test.cpp) -make_test(test_resource test_resource.cpp) +function(make_generic_test modelIdentifier sources) + set(target_name "${modelIdentifier}") + add_executable(${target_name} ${sources} "$") + add_test(NAME ${target_name} COMMAND ${target_name}) + target_link_libraries(${target_name} PUBLIC Catch2::Catch2WithMain) + target_compile_definitions(${target_name} + PRIVATE + FMU4CPP_MODEL_IDENTIFIER="${modelIdentifier}" + TEST_CASE_RESOURCE_LOCATION="${PROJECT_SOURCE_DIR}/export/tests/resources" + ) + target_include_directories(${target_name} + PRIVATE + "${PROJECT_SOURCE_DIR}/export/include" + "${PROJECT_SOURCE_DIR}/export/src" + ) + +endfunction() + +make_generic_test(basic_test basic_test.cpp) +make_generic_test(test_resource test_resource.cpp) + +add_subdirectory(fmi2) +add_subdirectory(fmi3) diff --git a/export/tests/fmi2/CMakeLists.txt b/export/tests/fmi2/CMakeLists.txt new file mode 100644 index 0000000..c1cb62c --- /dev/null +++ b/export/tests/fmi2/CMakeLists.txt @@ -0,0 +1,3 @@ + +make_test("fmi2" array_test array_test.cpp) +make_test("fmi2" identity_test identity_test.cpp) diff --git a/export/tests/array_test.cpp b/export/tests/fmi2/array_test.cpp similarity index 100% rename from export/tests/array_test.cpp rename to export/tests/fmi2/array_test.cpp index f6bc4a5..612b0dd 100644 --- a/export/tests/array_test.cpp +++ b/export/tests/fmi2/array_test.cpp @@ -69,9 +69,9 @@ TEST_CASE("test_array") { auto c = fmi2Instantiate("array", fmi2CoSimulation, guid.c_str(), "", &callbackFunctions, false, true); REQUIRE(c); + REQUIRE(fmi2SetupExperiment(c, false, 0, 0, false, 0) == fmi2OK); REQUIRE(fmi2EnterInitializationMode(c) == fmi2OK); REQUIRE(fmi2ExitInitializationMode(c) == fmi2OK); - REQUIRE(fmi2SetupExperiment(c, false, 0, 0, false, 0) == fmi2OK); std::vector values(4); for (int i = 1; i <= 4; i++) {// account for time diff --git a/export/tests/identity_test.cpp b/export/tests/fmi2/identity_test.cpp similarity index 100% rename from export/tests/identity_test.cpp rename to export/tests/fmi2/identity_test.cpp index 71b7ed1..f344805 100644 --- a/export/tests/identity_test.cpp +++ b/export/tests/fmi2/identity_test.cpp @@ -177,9 +177,9 @@ TEST_CASE("test_identity") { const auto c = fmi2Instantiate("identity", fmi2CoSimulation, guid.c_str(), "", &callbackFunctions, false, true); REQUIRE(c); + REQUIRE(fmi2SetupExperiment(c, false, 0, 0, false, 0) == fmi2OK); REQUIRE(fmi2EnterInitializationMode(c) == fmi2OK); REQUIRE(fmi2ExitInitializationMode(c) == fmi2OK); - REQUIRE(fmi2SetupExperiment(c, false, 0, 0, false, 0) == fmi2OK); REQUIRE(readReal(c, realOut->value_reference()) == Catch::Approx(0)); REQUIRE(readString(c, stringOut->value_reference()) == "empty"); diff --git a/export/tests/fmi3/CMakeLists.txt b/export/tests/fmi3/CMakeLists.txt new file mode 100644 index 0000000..462dcf4 --- /dev/null +++ b/export/tests/fmi3/CMakeLists.txt @@ -0,0 +1,3 @@ + +make_test("fmi3" array_test array_test.cpp) +make_test("fmi3" identity_test identity_test.cpp) diff --git a/export/tests/fmi3/array_test.cpp b/export/tests/fmi3/array_test.cpp new file mode 100644 index 0000000..f81b2bb --- /dev/null +++ b/export/tests/fmi3/array_test.cpp @@ -0,0 +1,91 @@ + +#include "fmi3/fmi3Functions.h" +#include "fmi3/fmi3Functions.h" + +#include +#include +#include + +#include +#include + +#include + +class Model : public fmu4cpp::fmu_base { + +public: + explicit Model(const fmu4cpp::fmu_data &data) + : fmu_base(data), reals_(4) { + + for (int i = 0; i < reals_.size(); i++) { + register_variable( + real("real[" + std::to_string(i) + "]", [this, i] { return reals_[i]; }, [this, i](double val) { reals_[i] = val; }).setCausality(fmu4cpp::causality_t::PARAMETER).setVariability(fmu4cpp::variability_t::TUNABLE)); + } + + Model::reset(); + } + + bool do_step(double dt) override { + return true; + } + + void reset() override { + reals_.assign({1, 2, 3, 4}); + } + +private: + std::vector reals_; +}; + +fmu4cpp::model_info fmu4cpp::get_model_info() { + model_info info; + info.modelName = "Array"; + info.description = "A simple model with arrays"; + info.modelIdentifier = FMU4CPP_MODEL_IDENTIFIER; + return info; +} + +FMU4CPP_INSTANTIATE(Model); + + +void fmilogger(fmi3InstanceEnvironment, fmi3Status status, fmi3String /*category*/, fmi3String message) { + + std::cout << status << ": " << message << std::endl; +} + +TEST_CASE("test_array") { + + Model model({}); + const auto guid = model.guid(); + + auto c = fmi3InstantiateCoSimulation("array", guid.c_str(), "", false, true, false, false, nullptr, 0, nullptr, fmilogger, nullptr); + REQUIRE(c); + + REQUIRE(fmi3EnterInitializationMode(c,false, 0, 0, false, 0) == fmi3OK); + REQUIRE(fmi3ExitInitializationMode(c) == fmi3OK); + + + std::vector values(4); + for (int i = 1; i <= 4; i++) {// account for time + fmi3ValueReference ref = i; + fmi3GetFloat64(c, &ref, 1, &values[i - 1], 1); + } + REQUIRE(values == std::vector{1, 2, 3, 4}); + + for (int i = 1; i <= 4; i++) { + fmi3ValueReference ref = i; + fmi3Float64 val = 9; + fmi3SetFloat64(c, &ref, 1, &val, 1); + } + + for (int i = 1; i <= 4; i++) { + fmi3ValueReference ref = i; + fmi3GetFloat64(c, &ref, 1, &values[i - 1], 1); + } + REQUIRE(values == std::vector{9, 9, 9, 9}); + + + REQUIRE(fmi3Terminate(c) == fmi3OK); + + fmi3FreeInstance(c); +} diff --git a/export/tests/fmi3/identity_test.cpp b/export/tests/fmi3/identity_test.cpp new file mode 100644 index 0000000..929c437 --- /dev/null +++ b/export/tests/fmi3/identity_test.cpp @@ -0,0 +1,216 @@ + +#include "catch2/matchers/catch_matchers_vector.hpp" +#include "fmi3/fmi3Functions.h" + + +#include +#include +#include +#include + +#include + +class Model : public fmu4cpp::fmu_base { + +public: + explicit Model(const fmu4cpp::fmu_data &data) : fmu_base(data) { + + register_variable(integer("integerIn", &integer_) + .setCausality(fmu4cpp::causality_t::INPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE)); + + register_variable(real("realIn", &real_) + .setCausality(fmu4cpp::causality_t::INPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE)); + + register_variable(boolean("booleanIn", &boolean_) + .setCausality(fmu4cpp::causality_t::INPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE)); + + register_variable(string("stringIn", &string_) + .setCausality(fmu4cpp::causality_t::INPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE)); + + + register_variable(integer("integerOut", &integer_) + .setCausality(fmu4cpp::causality_t::OUTPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE) + .setInitial(fmu4cpp::initial_t::CALCULATED) + .setDependencies({"integerIn"})); + + register_variable(real("realOut", &real_) + .setCausality(fmu4cpp::causality_t::OUTPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE) + .setInitial(fmu4cpp::initial_t::CALCULATED) + .setDependencies({"realIn"})); + + register_variable(boolean("booleanOut", &boolean_) + .setCausality(fmu4cpp::causality_t::OUTPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE) + .setInitial(fmu4cpp::initial_t::CALCULATED) + .setDependencies({"booleanIn"})); + + register_variable(string("stringOut", [this] { return string_; }) + .setCausality(fmu4cpp::causality_t::OUTPUT) + .setVariability(fmu4cpp::variability_t::DISCRETE) + .setInitial(fmu4cpp::initial_t::CALCULATED) + .setDependencies({"stringIn"})); + + Model::reset(); + } + + bool do_step(double dt) override { + return true; + } + + void reset() override { + integer_ = 0; + real_ = 0; + boolean_ = false; + string_ = "empty"; + } + +private: + int integer_; + double real_; + bool boolean_; + std::string string_; +}; + +fmu4cpp::model_info fmu4cpp::get_model_info() { + model_info info; + info.modelName = "Identity"; + info.description = "A simple feed-trough model"; + info.modelIdentifier = FMU4CPP_MODEL_IDENTIFIER; + return info; +} + +FMU4CPP_INSTANTIATE(Model); + + +int readInt(fmi3Instance c, fmi3ValueReference ref) { + fmi3Int32 value; + REQUIRE(fmi3GetInt32(c, &ref, 1, &value, 1) == fmi3OK); + + return value; +} + +void setInt(fmi3Instance c, fmi3ValueReference ref, int value) { + REQUIRE(fmi3SetInt32(c, &ref, 1, &value, 1) == fmi3OK); +} + +double readReal(fmi3Instance c, fmi3ValueReference ref) { + fmi3Float64 value; + REQUIRE(fmi3GetFloat64(c, &ref, 1, &value, 0) == fmi3OK); + + return value; +} + +void setReal(fmi3Instance c, fmi3ValueReference ref, double value) { + REQUIRE(fmi3SetFloat64(c, &ref, 1, &value, 0) == fmi3OK); +} + +bool readBool(fmi3Instance c, fmi3ValueReference ref) { + fmi3Boolean value; + REQUIRE(fmi3GetBoolean(c, &ref, 1, &value, 0) == fmi3OK); + + return value; +} + +void setBool(fmi3Instance c, fmi3ValueReference ref, bool value) { + fmi3Boolean value_ = value; + REQUIRE(fmi3SetBoolean(c, &ref, 1, &value_, 0) == fmi3OK); +} + + +std::string readString(fmi3Instance c, fmi3ValueReference ref) { + fmi3String value; + REQUIRE(fmi3GetString(c, &ref, 1, &value, 0) == fmi3OK); + + return value; +} + +void setString(fmi3Instance c, fmi3ValueReference ref, const std::string &value) { + fmi3String value_ = value.c_str(); + REQUIRE(fmi3SetString(c, &ref, 1, &value_, 0) == fmi3OK); +} + +void setOutputFail(fmi3Instance c) { + fmi3ValueReference ref = 999;// out of bounds + fmi3Int32 i = 0; + fmi3String s = ""; + fmi3Float64 r = 0; + fmi3Boolean b = fmi3False; + + REQUIRE(fmi3SetInt32(c, &ref, 1, &i, 0) == fmi3Error); + REQUIRE(fmi3SetFloat64(c, &ref, 1, &r, 0) == fmi3Error); + REQUIRE(fmi3SetString(c, &ref, 1, &s, 0) == fmi3Error); + REQUIRE(fmi3SetBoolean(c, &ref, 1, &b, 0) == fmi3Error); +} + +void fmilogger(fmi3InstanceEnvironment, fmi3Status status, fmi3String /*category*/, fmi3String message) { + + std::cout << status << ": " << message << std::endl; +} + +TEST_CASE("test_identity") { + + Model model({}); + const auto guid = model.guid(); + + const auto realIn = model.get_real_variable("realIn"); + const auto stringIn = model.get_string_variable("stringIn"); + const auto integerIn = model.get_int_variable("integerIn"); + const auto booleanIn = model.get_bool_variable("booleanIn"); + + const auto realOut = model.get_real_variable("realOut"); + const auto stringOut = model.get_string_variable("stringOut"); + const auto integerOut = model.get_int_variable("integerOut"); + const auto booleanOut = model.get_bool_variable("booleanOut"); + + const auto c = fmi3InstantiateCoSimulation("identity", guid.c_str(), "", false, true, false, false, nullptr, 0, nullptr, fmilogger, nullptr); + REQUIRE(c); + + REQUIRE(fmi3EnterInitializationMode(c, false, 0, 0, false, 0) == fmi3OK); + REQUIRE(fmi3ExitInitializationMode(c) == fmi3OK); + + REQUIRE(readReal(c, realOut->value_reference()) == Catch::Approx(0)); + REQUIRE(readString(c, stringOut->value_reference()) == "empty"); + REQUIRE(readInt(c, integerOut->value_reference()) == 0); + REQUIRE(readBool(c, booleanOut->value_reference()) == false); + + double t{0}; + const double dt{0.1}; + + bool b{false}; + int counter{0}; + while (t < 1) { + + setInt(c, integerIn->value_reference(), counter); + setReal(c, realIn->value_reference(), t); + setString(c, stringIn->value_reference(), std::to_string(t)); + setBool(c, booleanIn->value_reference(), b); + + bool eventhandlingNeeded; + bool terminateSimulation; + bool earlyReturn; + double lastSucessfulTime; + + REQUIRE(fmi3DoStep(c, t, dt, true, &eventhandlingNeeded, &terminateSimulation, &earlyReturn, &lastSucessfulTime) == fmi3OK); + + REQUIRE(readReal(c, realOut->value_reference()) == Catch::Approx(t)); + REQUIRE(readString(c, stringOut->value_reference()) == std::to_string(t)); + REQUIRE(readInt(c, integerOut->value_reference()) == counter); + REQUIRE(readBool(c, booleanOut->value_reference()) == b); + + t += dt; + counter++; + b = !b; + } + + setOutputFail(c); + + REQUIRE(fmi3Terminate(c) == fmi3OK); + + fmi3FreeInstance(c); +}