// // EDRAv2. Unit test // Command subsystem test // #include "pch.h" using namespace openEdr; CMD_DECLARE_LIBRARY_CLSID(CLSID_TestExtractContextCommand, 0xE52D3FA0); CMD_DECLARE_LIBRARY_CLSID(CLSID_TestReturnCommand, 0xE52D3FA1); CMD_DECLARE_LIBRARY_CLSID(CLSID_SimpleTestObject, 0xE52D3FA2); CMD_DECLARE_LIBRARY_CLSID(CLSID_FinalConstructCounter, 0xE52D3FA3); CMD_DECLARE_LIBRARY_CLSID(CLSID_TestErrorCommand, 0xE52D3FA4); namespace test_scenario { // // // class SimpleTestObject : public ObjectBase { public: }; // // // class FinalConstructCounter : public ObjectBase { public: void finalConstruct(Variant vCfg) { ++g_nActionCount; } static size_t g_nActionCount; static Variant createDataProxy(bool fCached) { return createObjProxy(Dictionary({ {"clsid", CLSID_FinalConstructCounter} }), fCached); } }; size_t FinalConstructCounter::g_nActionCount = 0; // // return getByPath from context. // Path is value passed into finalConstruct(in the "path" field. // class TestExtractContextCommand : public ObjectBase, public IContextCommand { private: std::string m_Path; public: void finalConstruct(Variant vConfig) { m_Path = vConfig["path"]; } virtual Variant execute(Variant vContext, Variant vParam) override { return getByPath(vContext, m_Path); } }; // // Return value passed into finalConstruct (in the "retvalue" field) // class TestReturnCommand : public ObjectBase, public ICommand { private: Variant m_vReturnedValue; public: void finalConstruct(Variant vConfig) { m_vReturnedValue = vConfig["retvalue"]; } virtual Variant execute(Variant vParam) override { return m_vReturnedValue; } virtual std::string getDescriptionString() override { return ""; } }; // // Return value passed into finalConstruct (in the "retvalue" field) // class TestErrorCommand : public ObjectBase, public ICommand { private: std::function m_pFn; public: void finalConstruct(Variant vConfig) { std::string sErrorMessage = vConfig.get("msg", "No error message"); m_pFn = [sErrorMessage]() { error::InvalidFormat(SL, sErrorMessage).throwException(); }; } virtual Variant execute(Variant vParam) override { m_pFn(); return {}; } virtual std::string getDescriptionString() override { return ""; } static ObjPtr create(std::function fn) { auto pThis = createObject(); pThis->m_pFn = fn; return pThis; } }; } // namespace test_scenario using namespace test_scenario; CMD_DEFINE_LIBRARY_CLASS(TestReturnCommand) CMD_DEFINE_LIBRARY_CLASS(TestExtractContextCommand) CMD_DEFINE_LIBRARY_CLASS(SimpleTestObject) CMD_DEFINE_LIBRARY_CLASS(FinalConstructCounter) CMD_DEFINE_LIBRARY_CLASS(TestErrorCommand) Variant execScenario(Variant vCode, Variant vContext = nullptr) { auto pScenario = queryInterface(createObject(CLSID_Scenario, Dictionary({ {"code", vCode} }))); return pScenario->execute(vContext); } // // // TEST_CASE("Scenario.set_action") { SECTION("src_is_scalar") { Variant vCode = Sequence ({ Dictionary({{"$set", 1}, {"$dst", "a"}}), }); Dictionary vContext; (void)execScenario(vCode, vContext); REQUIRE(vContext["a"] == 1); } SECTION("src_is_ContextCommand") { auto pCommand = createObject(Dictionary({ {"path", "a"} })); Variant vCode = Sequence({ Dictionary({{"$set", pCommand}, {"$dst", "b"}}) }); Dictionary vContext({ {"a", 1} }); REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 1); } SECTION("dst_is_absent") { Variant vCode = Sequence({ Dictionary({{"$set", 1}}), }); Dictionary vContext; REQUIRE_THROWS([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); } SECTION("dst_is_invalid") { Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", 1}}), }); Dictionary vContext; REQUIRE_THROWS([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); } SECTION("exception_process") { Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", 1}, {"$catch", nullptr}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); } SECTION("disabled") { Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$if", false}, {"$dst", "a"}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE_FALSE(vContext.has("a")); } } // // // TEST_CASE("Scenario.ret_action") { SECTION("with_scalar_value") { Variant vCode = Sequence({ Dictionary({{"$ret", 1}}), }); REQUIRE(execScenario(vCode) == 1); } SECTION("with_command_value") { auto pCommand = createObject(Dictionary({ {"path", "a"} })); Variant vCode = Sequence({ Dictionary({{"$ret", pCommand}}), }); Dictionary vContext({ {"a", 1} }); REQUIRE(execScenario(vCode, vContext) == 1); } SECTION("with_null") { Variant vCode = Sequence({ Dictionary({{"$ret", nullptr}}), }); REQUIRE(execScenario(vCode) == nullptr); } SECTION("exception_process") { Variant vCode = Sequence({ Dictionary({{"$ret", createObject()}, {"$catch", nullptr}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); } SECTION("disabled") { Variant vCode = Sequence({ Dictionary({{"$ret", 1}, {"$if", false}}), }); REQUIRE(execScenario(vCode) == nullptr); } } // // // TEST_CASE("Scenario.goto_action") { SECTION("branchname_is_string") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", "second"}}), Dictionary({{"$ret", 2}}), })}, {"second", Sequence({ Dictionary({{"$ret", 1}}), })}, }); REQUIRE(execScenario(vCode) == 1); } SECTION("branchname_is_int") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", 100}}), Dictionary({{"$ret", 2}}), })}, {"100", Sequence({ Dictionary({{"$ret", 1}}), })}, }); REQUIRE(execScenario(vCode) == 1); } SECTION("branchname_is_command") { auto pCommand = createObject(Dictionary({ {"path", "gotoBranch"} })); Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", pCommand}}), Dictionary({{"$ret", 2}}), })}, {"second", Sequence({ Dictionary({{"$ret", 1}}), })}, }); Dictionary vContext({ {"gotoBranch", "second"} }); REQUIRE(execScenario(vCode, vContext) == 1); } SECTION("invalid_value") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", 1}}), })}, }); REQUIRE_THROWS([vCode]() { (void)execScenario(vCode); }()); } SECTION("absent_branch") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", "second"}}), })}, }); REQUIRE_THROWS([vCode]() { (void)execScenario(vCode); }()); } SECTION("absent_branch_with_default") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", "third"}, {"$default", "second"} }), })}, {"second", Sequence({ Dictionary({{"$ret", 1}}), })}, }); REQUIRE(execScenario(vCode) == 1); } SECTION("absent_branch_with_absent_default") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", "third"}, {"$default", "second"} }), })}, }); REQUIRE_THROWS([vCode]() { (void)execScenario(vCode); }()); } SECTION("exception_process") { Variant vCode = Sequence({ Dictionary({{"$goto", createObject()}, {"$catch", nullptr}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); } SECTION("disabled") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", "second"}, {"$if", false}}), Dictionary({{"$ret", 2}}), })}, {"second", Sequence({ Dictionary({{"$ret", 1}}), })}, }); REQUIRE(execScenario(vCode) == 2); } } // // // TEST_CASE("Scenario.exec_action") { SECTION("exec_contain_cmd") { auto pCommand = createObject(Dictionary({ {"path", "a"} })); Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$exec", pCommand}, {"$dst", "b"}}), Dictionary({{"$ret", Dictionary({{"$path", "b"}}) }}), }); REQUIRE(execScenario(vCode) == 1); } SECTION("exec_contain_cmd_without_dst") { auto pCommand = createObject(Dictionary({ {"path", "a"} })); Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$exec", pCommand}}), Dictionary({{"$ret", Dictionary({{"$path", "b"}}) }}), }); REQUIRE(execScenario(vCode) == 2); } SECTION("exec_absent") { Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({ {"$dst", "b"}, {"clsid", CLSID_TestExtractContextCommand}, {"path", "a"}, }), Dictionary({{"$ret", Dictionary({{"$path", "b"}}) }}), }); REQUIRE(execScenario(vCode) == 1); } SECTION("exec_is_wrong") { Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({ {"$exec", 1}, {"$dst", "b"}, {"clsid", CLSID_TestExtractContextCommand}, {"path", "a"}, }), Dictionary({{"$ret", Dictionary({{"$path", "b"}}) }}), }); REQUIRE_THROWS([vCode]() { (void)execScenario(vCode); }()); } SECTION("exec_absent_invalid_object") { Variant vCode = Sequence({ Dictionary({ {"clsid", CLSID_SimpleTestObject}, }), }); REQUIRE_THROWS([vCode]() { (void)execScenario(vCode); }()); } SECTION("exception_process") { Variant vCode = Sequence({ Dictionary({ {"clsid", CLSID_TestErrorCommand}, {"$catch", nullptr} }), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); } SECTION("disabled") { auto pCommand = createObject(Dictionary({ {"path", "a"} })); Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$exec", pCommand}, {"$dst", "b"}, {"$if", false} }), Dictionary({{"$ret", Dictionary({{"$path", "b"}}) }}), }); REQUIRE(execScenario(vCode) == 2); } } // // // enum class CatchTestResult { ExceptionIsNotCatched, CompileError, GotoNextLine, GotoBranch, GotoUnknownBranch, }; // // // ObjPtr createAndCompileScenario(Variant vScenarioCode, bool fExpectException) { // Create and compile ObjPtr pScenario; if (fExpectException) { REQUIRE_THROWS([&vScenarioCode, &pScenario]() { pScenario = queryInterface(createObject(CLSID_Scenario, Dictionary({ {"code", vScenarioCode}, {"compileOnCall", true}, }))); }()); REQUIRE(pScenario == nullptr); } else { REQUIRE_NOTHROW([&vScenarioCode, &pScenario]() { pScenario = queryInterface(createObject(CLSID_Scenario, Dictionary({ {"code", vScenarioCode}, {"compileOnCall", true}, }))); }()); REQUIRE(pScenario != nullptr); } return pScenario; } // // // void testCatchCodeField(std::optional vCode, CatchTestResult eExpectedResult) { Variant vScenarioCode = Sequence({ Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", (vCode.has_value() ? Dictionary({{"code", vCode.value()}}) : Dictionary()) }}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); auto pScenario = createAndCompileScenario(vScenarioCode, eExpectedResult == CatchTestResult::CompileError); if (pScenario == nullptr) return; if (eExpectedResult == CatchTestResult::GotoNextLine) { Dictionary vContext({ {"b", 1} }); REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext);}()); REQUIRE(vContext["b"] == 2); } else if (eExpectedResult == CatchTestResult::ExceptionIsNotCatched) { Dictionary vContext({ {"b", 1} }); REQUIRE_THROWS([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["b"] == 1); } else { FAIL("Invalid eExpectedResult"); } } // // // void testCatchLogField(std::optional vLog, CatchTestResult eExpectedResult) { Variant vScenarioCode = Sequence({ Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", (vLog.has_value() ? Dictionary({{"log", vLog.value()}}) : Dictionary()) }}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); auto pScenario = createAndCompileScenario(vScenarioCode, eExpectedResult == CatchTestResult::CompileError); if (pScenario == nullptr) return; if (eExpectedResult == CatchTestResult::GotoNextLine) { Dictionary vContext({ {"b", 1} }); REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext);}()); REQUIRE(vContext["b"] == 2); } else { FAIL("Invalid eExpectedResult"); } } // // // void testCatchGotoField(std::optional vGoto, CatchTestResult eExpectedResult) { Variant vScenarioCode = Dictionary({ {"main", Sequence({ Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", (vGoto.has_value() ? Dictionary({{"goto", vGoto.value()}}) : Dictionary()) }}), Dictionary({{"$set", 3}, {"$dst", "b"}}), })}, {"branch", Sequence({ Dictionary({{"$set", 4}, {"$dst", "b"}}), })}, }); auto pScenario = createAndCompileScenario(vScenarioCode, eExpectedResult == CatchTestResult::CompileError); if (pScenario == nullptr) return; if (eExpectedResult == CatchTestResult::ExceptionIsNotCatched) { Dictionary vContext({ {"b", 1} }); REQUIRE_THROWS([&vContext, &pScenario]() { pScenario->execute(vContext);}()); REQUIRE(vContext["b"] == 2); } else if (eExpectedResult == CatchTestResult::GotoNextLine) { Dictionary vContext({ {"b", 1} }); REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["b"] == 3); } else if (eExpectedResult == CatchTestResult::GotoBranch) { Dictionary vContext({ {"b", 1} }); REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["b"] == 4); } else if (eExpectedResult == CatchTestResult::GotoUnknownBranch) { Dictionary vContext({ {"b", 1} }); REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["b"] == 2); } else { FAIL("Invalid eExpectedResult"); } } // // // void testCatchDstField(std::optional vDst, CatchTestResult eExpectedResult, bool fSerializeException) { Variant vScenarioCode = Sequence({ Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", (vDst.has_value() ? Dictionary({{"dst", vDst.value()}}) : Dictionary()) }}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); auto pScenario = createAndCompileScenario(vScenarioCode, eExpectedResult == CatchTestResult::CompileError); if (pScenario == nullptr) return; if (eExpectedResult == CatchTestResult::ExceptionIsNotCatched) { Dictionary vContext({ {"b", 1} }); REQUIRE_THROWS([&vContext, &pScenario]() { pScenario->execute(vContext);}()); REQUIRE(vContext["b"] == 1); } else if (eExpectedResult == CatchTestResult::GotoNextLine) { Dictionary vContext({ {"b", 1} }); REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["b"] == 2); if (!fSerializeException) { REQUIRE(vContext.getSize() == 1); } else { REQUIRE(vContext.getSize() == 2); std::string sDst = vDst.value(); Variant vExceptionCode; REQUIRE_NOTHROW([&vContext, &vExceptionCode, &sDst]() { vExceptionCode = variant::getByPath(vContext, sDst).get("errCode"); }()); REQUIRE(vExceptionCode == error::ErrorCode::InvalidFormat); } } else { FAIL("Invalid eExpectedResult"); } } // // // ObjPtr createScenarioWithSequenceCatch(error::ErrorCode eThrownException, bool fAddDefaultRule) { Variant vCatch = Sequence({ Dictionary({ {"code", error::ErrorCode::OutOfRange}, {"goto", "OutOfRange"}, {"dst", "OutOfRange"} }), Dictionary({ {"code", error::ErrorCode::InvalidArgument}, {"goto", "InvalidArgument"}, {"dst", "InvalidArgument"} }), }); if (fAddDefaultRule) { vCatch.push_back(Dictionary({ {"goto", "All"}, {"dst", "All"} })); } Variant vExceptionCmd = TestErrorCommand::create([eThrownException]() { if (eThrownException == error::ErrorCode::OutOfRange) error::OutOfRange(SL, "msg").throwException(); if (eThrownException == error::ErrorCode::InvalidArgument) error::InvalidArgument(SL, "msg").throwException(); if (eThrownException == error::ErrorCode::NotFound) error::NotFound(SL, "msg").throwException(); }); Variant vScenarioCode = Dictionary({ {"main", Sequence({ Dictionary({{"$set", 1}, {"$dst", "d"}}), Dictionary({{"$set", vExceptionCmd}, {"$dst", "a"}, {"$catch", vCatch}}), Dictionary({{"$set", 2}, {"$dst", "d"}}), })}, {"OutOfRange", Sequence({ Dictionary({{"$set", 3}, {"$dst", "d"}}), })}, {"InvalidArgument", Sequence({ Dictionary({{"$set", 4}, {"$dst", "d"}}), })}, {"All", Sequence({ Dictionary({{"$set", 5}, {"$dst", "d"}}), })}, }); auto pScenario = createAndCompileScenario(vScenarioCode, false); REQUIRE(pScenario != nullptr); return pScenario; } // // // TEST_CASE("Scenario.catch_field") { SECTION("no_exception") { Variant vCode = Sequence ({ Dictionary({{"$set", 1}, {"$dst", "a"}, {"$catch", nullptr}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["a"] == 1); } SECTION("no_catch") { Variant vCode = Sequence({ Dictionary({{"$set", createObject()}, {"$dst", "a"}}), }); Dictionary vContext; REQUIRE_THROWS([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); } SECTION("catch_is_null") { Variant vCode = Sequence({ Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", nullptr}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); } SECTION("catch_is_valid_string") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", "onexception"}}), Dictionary({{"$set", 3}, {"$dst", "b"}}), })}, {"onexception", Sequence({ Dictionary({{"$set", 4}, {"$dst", "d"}}), })}, }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); REQUIRE(vContext["d"] == 4); } SECTION("catch_is_invalid_string") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", "invalid_branch"}}), Dictionary({{"$set", 3}, {"$dst", "b"}}), })}, {"onexception", Sequence({ Dictionary({{"$set", 4}, {"$dst", "d"}}), })}, }); Dictionary vContext; REQUIRE_THROWS([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); } SECTION("catch_has_invalid_type") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", true}}), Dictionary({{"$set", 3}, {"$dst", "b"}}), })}, {"onexception", Sequence({ Dictionary({{"$set", 4}, {"$dst", "d"}}), })}, }); Dictionary vContext; REQUIRE_THROWS([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); } SECTION("exception_in_if") { Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$if", createObject()}, {"$set", 2}, {"$dst", "a"}, {"$catch", nullptr}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); Dictionary vContext; REQUIRE_NOTHROW([vCode, vContext]() { (void)execScenario(vCode, vContext); }()); REQUIRE(vContext["b"] == 2); REQUIRE(vContext["a"] == 1); } SECTION("code_is_absent") { testCatchCodeField(std::nullopt, CatchTestResult::GotoNextLine); } SECTION("code_is_zero_int") { testCatchCodeField(Variant(0), CatchTestResult::GotoNextLine); } SECTION("code_is_empty_string") { testCatchCodeField(Variant(""), CatchTestResult::GotoNextLine); } SECTION("code_is_valid_int_catched") { testCatchCodeField(Variant(error::ErrorCode::InvalidFormat), CatchTestResult::GotoNextLine); } SECTION("code_is_valid_int_catched_based") { testCatchCodeField(Variant(error::ErrorCode::RuntimeError), CatchTestResult::GotoNextLine); } SECTION("code_is_valid_int_uncatched") { testCatchCodeField(Variant(error::ErrorCode::AccessDenied), CatchTestResult::ExceptionIsNotCatched); } SECTION("code_is_valid_string_catched") { testCatchCodeField(Variant("0xE0020005"), CatchTestResult::GotoNextLine); } SECTION("code_is_valid_string_catched_based") { testCatchCodeField(Variant("0xE0020000"), CatchTestResult::GotoNextLine); } SECTION("code_is_valid_string_uncatched") { testCatchCodeField(Variant("0xE0020001"), CatchTestResult::ExceptionIsNotCatched); } SECTION("code_is_invalid_string") { testCatchCodeField(Variant("QWERTY"), CatchTestResult::CompileError); } SECTION("code_is_invalid_type") { testCatchCodeField(Variant(false), CatchTestResult::CompileError); } SECTION("log_is_absent") { testCatchLogField(std::nullopt, CatchTestResult::GotoNextLine); } SECTION("log_is_true") { testCatchLogField(Variant(true), CatchTestResult::GotoNextLine); } SECTION("log_is_false") { testCatchLogField(Variant(false), CatchTestResult::GotoNextLine); } SECTION("log_is_invalid") { testCatchLogField(Variant(""), CatchTestResult::CompileError); } SECTION("goto_is_absent") { testCatchGotoField(std::nullopt, CatchTestResult::GotoNextLine); } SECTION("goto_is_empty_string") { testCatchGotoField(Variant(""), CatchTestResult::GotoNextLine); } SECTION("goto_is_valid_string") { testCatchGotoField(Variant("branch"), CatchTestResult::GotoBranch); } SECTION("goto_is_invalid_string") { testCatchGotoField(Variant("xxxx"), CatchTestResult::ExceptionIsNotCatched); } SECTION("goto_is_invalid_type") { testCatchGotoField(Variant(false), CatchTestResult::CompileError); } SECTION("dst_is_invalid_type") { testCatchDstField(Variant(false), CatchTestResult::CompileError, false); } SECTION("dst_is_absent") { testCatchDstField(std::nullopt, CatchTestResult::GotoNextLine, false); } SECTION("dst_is_valid_simple_path") { testCatchDstField(Variant("z"), CatchTestResult::GotoNextLine, true); } SECTION("dst_is_valid_complex_path") { testCatchDstField(Variant("z.x.y"), CatchTestResult::GotoNextLine, true); } SECTION("dst_is_invalid_path") { testCatchDstField(Variant(".."), CatchTestResult::ExceptionIsNotCatched, false); } SECTION("catch_is_invalid_sequence") { Variant vScenarioCode = Sequence({ Dictionary({{"$set", createObject()}, {"$dst", "a"}, {"$catch", Sequence({false}) }}), Dictionary({{"$set", 2}, {"$dst", "b"}}), }); auto pScenario = createAndCompileScenario(vScenarioCode, true); REQUIRE(pScenario == nullptr); } SECTION("catch_is_sequence_without_default_and_first_catch") { auto pScenario = createScenarioWithSequenceCatch(error::ErrorCode::OutOfRange, false); Dictionary vContext; REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["d"] == 3); REQUIRE(vContext["OutOfRange"]["errCode"] == error::ErrorCode::OutOfRange); REQUIRE_FALSE(vContext.has("InvalidArgument")); REQUIRE_FALSE(vContext.has("All")); } SECTION("catch_is_sequence_without_default_and_second_catch") { auto pScenario = createScenarioWithSequenceCatch(error::ErrorCode::InvalidArgument, false); Dictionary vContext; REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["d"] == 4); REQUIRE_FALSE(vContext.has("OutOfRange")); REQUIRE(vContext["InvalidArgument"]["errCode"] == error::ErrorCode::InvalidArgument); REQUIRE_FALSE(vContext.has("All")); } SECTION("catch_is_sequence_without_default_and_no_catch") { auto pScenario = createScenarioWithSequenceCatch(error::ErrorCode::NotFound, false); Dictionary vContext; REQUIRE_THROWS([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["d"] == 1); REQUIRE_FALSE(vContext.has("OutOfRange")); REQUIRE_FALSE(vContext.has("InvalidArgument")); REQUIRE_FALSE(vContext.has("All")); } SECTION("catch_is_sequence_with_default_and_first_catch") { auto pScenario = createScenarioWithSequenceCatch(error::ErrorCode::OutOfRange, true); Dictionary vContext; REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["d"] == 3); REQUIRE(vContext["OutOfRange"]["errCode"] == error::ErrorCode::OutOfRange); REQUIRE_FALSE(vContext.has("InvalidArgument")); REQUIRE_FALSE(vContext.has("All")); } SECTION("catch_is_sequence_with_default_and_default_catch") { auto pScenario = createScenarioWithSequenceCatch(error::ErrorCode::NotFound, true); Dictionary vContext; REQUIRE_NOTHROW([&vContext, &pScenario]() { pScenario->execute(vContext); }()); REQUIRE(vContext["d"] == 5); REQUIRE_FALSE(vContext.has("OutOfRange")); REQUIRE_FALSE(vContext.has("InvalidArgument")); REQUIRE(vContext["All"]["errCode"] == error::ErrorCode::NotFound); } } // // // TEST_CASE("Scenario.finalConstruct") { SECTION("name_is_exist") { char sScenarioName[] = "ExampleScenarioName"; Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), }); Variant vCfg = Dictionary({ {"code", vCode}, {"name", sScenarioName}, }); auto pScenario = queryInterface(createObject(CLSID_Scenario, vCfg)); REQUIRE(pScenario->getDescriptionString() == sScenarioName); } SECTION("name_is_absent") { Variant vCode = Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), }); Variant vCfg = Dictionary({ {"code", vCode}, }); auto pScenario = queryInterface(createObject(CLSID_Scenario, vCfg)); std::string sScenarioName = FMT("sid-" << hex(pScenario->getId())); REQUIRE(pScenario->getDescriptionString() == sScenarioName); } SECTION("code_is_Sequence") { Variant vCode = Sequence({ Dictionary({{"$ret", 1}}), }); REQUIRE(execScenario(vCode) == 1); } SECTION("code_is_Dictionary") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$goto", "a"}}) })}, {"a", Sequence({ Dictionary({{"$goto", "b"}}) })}, {"b", Sequence({ Dictionary({{"$ret", 1}}) })}, }); REQUIRE(execScenario(vCode) == 1); } SECTION("code_is_Dictionary_without_main") { Variant vCode = Dictionary({ {"b", Sequence({ Dictionary({{"$ret", 1}}) })}, }); REQUIRE_THROWS_AS([vCode]() { (void)execScenario(vCode); }(), error::LogicError); } SECTION("code_is_invalid_type") { Variant vCode = 1; REQUIRE_THROWS_AS([vCode]() { (void)execScenario(vCode); }(), error::LogicError); } SECTION("addParams_is_true") { Variant vCode = Sequence({ Dictionary({{ "$ret", Dictionary({{"$path", "params"}, {"$default", 0}}) }}), }); Variant vCfg = Dictionary({ {"code", vCode}, {"addParams", true}, }); auto pCmd = queryInterface(createObject(CLSID_Scenario, vCfg)); REQUIRE(pCmd->execute(1) == 1); auto pCtxCmd = queryInterface(pCmd); REQUIRE(pCtxCmd->execute(Dictionary(), 1) == 1); } SECTION("addParams_is_false") { Variant vCode = Sequence({ Dictionary({{ "$ret", Dictionary({{"$path", "params"}, {"$default", 0}}) }}), }); Variant vCfg = Dictionary({ {"code", vCode}, {"addParams", false}, }); auto pCmd = queryInterface(createObject(CLSID_Scenario, vCfg)); REQUIRE(pCmd->execute(1) == 0); auto pCtxCmd = queryInterface(pCmd); REQUIRE(pCtxCmd->execute(Dictionary(), 1) == 0); } SECTION("addParams_is_absent") { Variant vCode = Sequence({ Dictionary({{ "$ret", Dictionary({{"$path", "params"}, {"$default", 0}}) }}), }); Variant vCfg = Dictionary({ {"code", vCode}, }); auto pCmd = queryInterface(createObject(CLSID_Scenario, vCfg)); REQUIRE(pCmd->execute(1) == 0); auto pCtxCmd = queryInterface(pCmd); REQUIRE(pCtxCmd->execute(Dictionary(), 1) == 0); } } // // // TEST_CASE("Scenario.compilation") { FinalConstructCounter::g_nActionCount = 0; Variant vLazyFinalConstructCounter = FinalConstructCounter::createDataProxy(false); Variant vCode = Sequence({ Dictionary({{"$ret", vLazyFinalConstructCounter}, {"$dst", "a"}}), }); REQUIRE(FinalConstructCounter::g_nActionCount == 0); SECTION("compile_on_first_exec") { auto pScenario = queryInterface(createObject(CLSID_Scenario, Dictionary({ {"code", vCode}, {"compileOnCall", true} }))); REQUIRE(FinalConstructCounter::g_nActionCount == 1); // First call check { ObjPtr pObj = pScenario->execute(nullptr); REQUIRE(pObj->getClassId() == CLSID_FinalConstructCounter); REQUIRE(FinalConstructCounter::g_nActionCount == 1); } // Second call check { ObjPtr pObj = pScenario->execute(nullptr); REQUIRE(pObj->getClassId() == CLSID_FinalConstructCounter); REQUIRE(FinalConstructCounter::g_nActionCount == 1); } } SECTION("compile_on_finalConstruct") { auto pScenario = queryInterface(createObject(CLSID_Scenario, Dictionary({ {"code", vCode} }))); REQUIRE(FinalConstructCounter::g_nActionCount == 0); // First call check { ObjPtr pObj = pScenario->execute(nullptr); REQUIRE(pObj->getClassId() == CLSID_FinalConstructCounter); REQUIRE(FinalConstructCounter::g_nActionCount == 1); } // Second call check { ObjPtr pObj = pScenario->execute(nullptr); REQUIRE(pObj->getClassId() == CLSID_FinalConstructCounter); REQUIRE(FinalConstructCounter::g_nActionCount == 1); } } } // // // TEST_CASE("Scenario.execute") { SECTION("pass_context") { Variant vCode = Sequence({ Dictionary({{"$ret", Dictionary({{"$path", "a"}}) }}), }); Dictionary vContext({ {"a", 1} }); REQUIRE(execScenario(vCode, vContext) == 1); } SECTION("pass_null_context") { Variant vCode = Sequence({ Dictionary({{"$ret", Dictionary({{"$path", ""}}) }}), }); REQUIRE(execScenario(vCode, nullptr) == Dictionary()); } SECTION("execution_without_return") { Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$set", 3}, {"$dst", "c"}}), })}, {"second", Sequence({ Dictionary({{"$set", 4}, {"$dst", "d"}}), })}, }); Dictionary vContext; REQUIRE(execScenario(vCode, vContext) == nullptr); REQUIRE(vContext == Dictionary({ {"a",1}, {"b",2}, {"c",3} })); } } // // // enum class MultithreadCounters { Run = 0, InvalidResult, InvalidContext, Exception, Max }; static std::atomic_uint32_t g_eMultithreadCounters[(size_t)MultithreadCounters::Max]; void runScenarioSeveralTimes(ObjPtr pScenario, Size nRunCount) { Variant vEthalonContext = Dictionary({ {"a", 11}, {"b", 22}, {"c", 33}, {"aa", 1} }); for (Size i = 0; i < nRunCount; ++i) { try { Dictionary vContext; if (pScenario->execute(vContext, nullptr) != 1) ++g_eMultithreadCounters[(size_t)MultithreadCounters::InvalidResult]; if (vContext != vEthalonContext) ++g_eMultithreadCounters[(size_t)MultithreadCounters::InvalidContext]; } catch (error::Exception& e) { e.log(); ++g_eMultithreadCounters[(size_t)MultithreadCounters::Exception]; } catch (...) { ++g_eMultithreadCounters[(size_t)MultithreadCounters::Exception]; } ++g_eMultithreadCounters[(size_t)MultithreadCounters::Run]; } } // // // TEST_CASE("Scenario.multithread_execute") { auto pCommand = createObject(Dictionary({ {"path", "a"} })); Variant vCode = Dictionary({ {"main", Sequence({ Dictionary({{"$set", 1}, {"$dst", "a"}}), Dictionary({{"$set", 2}, {"$dst", "b"}}), Dictionary({{"$set", 3}, {"$dst", "c"}}), Dictionary({{"$exec", pCommand}, {"$dst", "aa"}}), Dictionary({{"$goto", "second"}}), })}, {"second", Sequence({ Dictionary({{"$set", 11}, {"$dst", "a"}}), Dictionary({{"$set", 22}, {"$dst", "b"}}), Dictionary({{"$set", 33}, {"$dst", "c"}}), Dictionary({{"$ret", Dictionary({{"$path", "aa"}}) }}), })}, }); auto pScenario = queryInterface(createObject(CLSID_Scenario, Dictionary({ {"code", vCode} }))); static constexpr Size c_nRunCount = 2000; static constexpr Size c_nThreadCount = 10; std::thread testThreads[c_nThreadCount]; for (auto& testThread : testThreads) testThread = std::thread(runScenarioSeveralTimes, pScenario, c_nRunCount); for (auto& testThread : testThreads) testThread.join(); CHECK(g_eMultithreadCounters[(size_t)MultithreadCounters::Run] == c_nRunCount * c_nThreadCount); CHECK(g_eMultithreadCounters[(size_t)MultithreadCounters::InvalidContext] == 0); CHECK(g_eMultithreadCounters[(size_t)MultithreadCounters::InvalidResult] == 0); CHECK(g_eMultithreadCounters[(size_t)MultithreadCounters::Exception] == 0); }