/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include "PythonTraceMessageSink.h" #include "PythonTestingUtility.h" #include #include #include #include #include #include namespace UnitTest { struct EditorPythonBindingsNotificationBusSink final : public EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler { EditorPythonBindingsNotificationBusSink() { EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusConnect(); } ~EditorPythonBindingsNotificationBusSink() { EditorPythonBindings::EditorPythonBindingsNotificationBus::Handler::BusDisconnect(); } ////////////////////////////////////////////////////////////////////////// // handles EditorPythonBindingsNotificationBus int m_OnPreInitializeCount = 0; int m_OnPostInitializeCount = 0; int m_OnPreFinalizeCount = 0; int m_OnPostFinalizeCount = 0; void OnPreInitialize() override { m_OnPreInitializeCount++; } void OnPostInitialize() override { m_OnPostInitializeCount++; } void OnPreFinalize() override { m_OnPreFinalizeCount++; } void OnPostFinalize() override { m_OnPostFinalizeCount++; } }; class EditorPythonBindingsTest : public PythonTestingFixture { public: PythonTraceMessageSink m_testSink; EditorPythonBindingsNotificationBusSink m_notificationSink; void SetUp() override { PythonTestingFixture::SetUp(); m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonSystemComponent::CreateDescriptor()); } void TearDown() override { // clearing up memory m_notificationSink = EditorPythonBindingsNotificationBusSink(); m_testSink = PythonTraceMessageSink(); // shutdown time! PythonTestingFixture::TearDown(); } }; TEST_F(EditorPythonBindingsTest, FireUpPythonVM) { enum class LogTypes { Skip = 0, General, RedirectOutputInstalled }; m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int { if (AzFramework::StringFunc::Equal(window, "python")) { if (AzFramework::StringFunc::Equal(message, "RedirectOutput installed")) { return static_cast(LogTypes::RedirectOutputInstalled); } return static_cast(LogTypes::General); } return static_cast(LogTypes::Skip); }; AZ::Entity e; e.CreateComponent(); e.Init(); e.Activate(); SimulateEditorBecomingInitialized(); e.Deactivate(); EXPECT_GT(m_testSink.m_evaluationMap[(int)LogTypes::General], 0); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::RedirectOutputInstalled], 1); EXPECT_EQ(m_notificationSink.m_OnPreInitializeCount, 1); EXPECT_EQ(m_notificationSink.m_OnPostFinalizeCount, 1); EXPECT_EQ(m_notificationSink.m_OnPreFinalizeCount, 1); EXPECT_EQ(m_notificationSink.m_OnPostFinalizeCount, 1); } TEST_F(EditorPythonBindingsTest, RunScriptTextBuffer) { enum class LogTypes { Skip = 0, ScriptWorked }; m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int { if (AzFramework::StringFunc::Equal(window, "python")) { if (AzFramework::StringFunc::Equal(message, "EditorPythonBindingsTest_RunScriptTextBuffer")) { return static_cast(LogTypes::ScriptWorked); } } return static_cast(LogTypes::Skip); }; AZ::Entity e; e.CreateComponent(); e.Init(); e.Activate(); SimulateEditorBecomingInitialized(); const char* script = R"( import sys print ('EditorPythonBindingsTest_RunScriptTextBuffer') )"; AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, false); e.Deactivate(); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::ScriptWorked], 1); } TEST_F(EditorPythonBindingsTest, RunScriptTextBufferAndPrint) { AZ::Entity e; e.CreateComponent(); e.Init(); e.Activate(); SimulateEditorBecomingInitialized(); AZStd::string capturedOutput; m_testSink.m_evaluateMessage = [&capturedOutput](const char* window, const char* message) -> int { if (AzFramework::StringFunc::Equal(window, "python")) { capturedOutput.append(message); } return 0; }; // Expressions should log their result // Any other statement shouldn't log anything capturedOutput.clear(); const char* script = "5+5"; AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true); EXPECT_EQ(capturedOutput, "10\n"); capturedOutput.clear(); script = R"( import sys sys.version )"; AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true); EXPECT_EQ(capturedOutput, ""); capturedOutput.clear(); script = "variable = 'test'"; AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true); EXPECT_EQ(capturedOutput, ""); capturedOutput.clear(); script = "variable"; AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script, true); EXPECT_EQ(capturedOutput, "test\n"); } TEST_F(EditorPythonBindingsTest, RunScriptFile) { enum class LogTypes { Skip = 0, RanFromFile }; m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int { if (AzFramework::StringFunc::Equal(window, "python")) { AZStd::string_view m(message); if (AzFramework::StringFunc::Equal(message, "EditorPythonBindingsTest_RunScriptFile")) { return static_cast(LogTypes::RanFromFile); } } return static_cast(LogTypes::Skip); }; AZStd::string filename; AzFramework::StringFunc::Path::ConstructFull(m_engineRoot, "Gems/EditorPythonBindings/Code/Tests", "EditorPythonBindingsTest", "py", filename); AZ::Entity e; e.CreateComponent(); e.Init(); e.Activate(); SimulateEditorBecomingInitialized(); AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilename, filename.c_str()); e.Deactivate(); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::RanFromFile], 1); } TEST_F(EditorPythonBindingsTest, RunScriptFileWithArgs) { enum class LogTypes { Skip = 0, RanFromFile, NumArgsCorrect, ScriptNameCorrect, Arg1Correct, Arg2Correct, Arg3Correct }; m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int { if (AzFramework::StringFunc::Equal(window, "python")) { AZStd::string_view m(message); if (AzFramework::StringFunc::Equal(message, "EditorPythonBindingsTestWithArgs_RunScriptFile")) { return static_cast(LogTypes::RanFromFile); } else if (AzFramework::StringFunc::Equal(message, "num args: 4")) { return static_cast(LogTypes::NumArgsCorrect); } else if (AzFramework::StringFunc::Equal(message, "script name: EditorPythonBindingsTestWithArgs.py")) { return static_cast(LogTypes::ScriptNameCorrect); } else if (AzFramework::StringFunc::Equal(message, "arg 1: arg1")) { return static_cast(LogTypes::Arg1Correct); } else if (AzFramework::StringFunc::Equal(message, "arg 2: 2")) { return static_cast(LogTypes::Arg2Correct); } else if (AzFramework::StringFunc::Equal(message, "arg 3: arg3")) { return static_cast(LogTypes::Arg3Correct); } } return static_cast(LogTypes::Skip); }; AZStd::string filename; AzFramework::StringFunc::Path::ConstructFull(m_engineRoot, "Gems/EditorPythonBindings/Code/Tests", "EditorPythonBindingsTestWithArgs", "py", filename); AZ::Entity e; e.CreateComponent(); e.Init(); e.Activate(); SimulateEditorBecomingInitialized(); AZStd::vector args; args.push_back("arg1"); args.push_back("2"); args.push_back("arg3"); AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, filename.c_str(), args); e.Deactivate(); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::RanFromFile], 1); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::NumArgsCorrect], 1); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::ScriptNameCorrect], 1); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::Arg1Correct], 1); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::Arg2Correct], 1); EXPECT_EQ(m_testSink.m_evaluationMap[(int)LogTypes::Arg3Correct], 1); } // // Tests that makes sure that basic Python libraries can be loaded // class EditorPythonBindingsLibraryTest : public PythonTestingFixture { public: PythonTraceMessageSink m_testSink; EditorPythonBindingsNotificationBusSink m_notificationSink; void SetUp() override { PythonTestingFixture::SetUp(); m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonSystemComponent::CreateDescriptor()); } void TearDown() override { // clearing up memory m_notificationSink = EditorPythonBindingsNotificationBusSink(); m_testSink = PythonTraceMessageSink(); // shutdown time! PythonTestingFixture::TearDown(); } void DoLibraryTest(const char* libName) { bool executedLine = false; m_testSink.m_evaluateMessage = [&executedLine](const char* window, const char* message) { if (AzFramework::StringFunc::Equal(window, "python")) { if (AzFramework::StringFunc::Equal(message, "python_vm_loaded_lib")) { executedLine = true; } } return false; }; AZ::Entity e; e.CreateComponent(); e.Init(); e.Activate(); try { SimulateEditorBecomingInitialized(); const bool printResult = false; AZStd::string script(AZStd::string::format("import %s\nprint ('python_vm_loaded_lib')", libName)); AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast( &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByString, script.c_str(), printResult); } catch (const std::exception& e) { AZ_Error("UnitTest", false, "Failed on with Python exception: %s", e.what()); } e.Deactivate(); EXPECT_TRUE(executedLine); } }; TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_sys_Works) { DoLibraryTest("sys"); } TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_ctypes_Works) { DoLibraryTest("ctypes"); } TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_bz2_Works) { DoLibraryTest("bz2"); } TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_lzma_Works) { DoLibraryTest("lzma"); } TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_socket_Works) { DoLibraryTest("socket"); } TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_sqlite3_Works) { DoLibraryTest("sqlite3"); } TEST_F(EditorPythonBindingsLibraryTest, PythonVMLoads_ssl_Works) { DoLibraryTest("ssl"); } } AZ_UNIT_TEST_HOOK();