/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #pragma once #include #include #include #include AZ_PUSH_DISABLE_WARNING(4389 4800, "-Wunknown-warning-option"); // 'int' : forcing value to bool 'true' or 'false' (performance warning). #undef strdup // This define is required by googletest #include #include AZ_POP_DISABLE_WARNING; #if defined(HAVE_BENCHMARK) #include #endif #include #define AZTEST_DLL_PUBLIC AZ_DLL_EXPORT #define AZTEST_EXPORT extern "C" AZTEST_DLL_PUBLIC namespace AZ { namespace Test { //! Forward declarations class Platform; /*! * Implement this interface to define the environment setup and teardown functions. */ class ITestEnvironment : public ::testing::Environment { public: virtual ~ITestEnvironment() {} void SetUp() final { SetupEnvironment(); } void TearDown() final { TeardownEnvironment(); } protected: virtual void SetupEnvironment() = 0; virtual void TeardownEnvironment() = 0; }; extern ::testing::Environment* sTestEnvironment; /*! * Monolithic builds will have all the environments available. Keep a mapping to run the desired envs. */ class TestEnvironmentRegistry { public: TestEnvironmentRegistry(std::vector a_envs, const std::string& a_module_name, bool a_unit) : m_module_name(a_module_name) , m_envs(a_envs) , m_unit(a_unit) { s_envs.push_back(this); } const std::string m_module_name; std::vector m_envs; bool m_unit; static std::vector s_envs; private: TestEnvironmentRegistry& operator=(const TestEnvironmentRegistry& tmp); }; /*! * Empty implementation of ITestEnvironment. */ class EmptyTestEnvironment final : public ITestEnvironment { public: virtual ~EmptyTestEnvironment() {} protected: void SetupEnvironment() override {} void TeardownEnvironment() override {} }; void addTestEnvironment(ITestEnvironment* env); void addTestEnvironments(std::vector envs); void excludeIntegTests(); //! A hook that can be used to read any other misc parameters and remove them before google sees them. //! Note that this modifies argc and argv to delete the parameters it consumes. void ApplyGlobalParameters(int* argc, char** argv); void printUnusedParametersWarning(int argc, char** argv); /*! * Main method for running tests from an executable. */ class AzUnitTestMain final { public: AzUnitTestMain(std::vector envs) : m_returnCode(0) , m_envs(envs) {} bool Run(int argc, char** argv); bool Run(const char* commandLine); int ReturnCode() const { return m_returnCode; } private: int m_returnCode; std::vector m_envs; }; //! Run tests in a single library by loading it dynamically and executing the exported symbol, //! passing main-like parameters (argc, argv) from the (real or artificial) command line. int RunTestsInLib(Platform& platform, const std::string& lib, const std::string& symbol, int& argc, char** argv); #if defined(HAVE_BENCHMARK) static constexpr const char* s_benchmarkEnvironmentName = "BenchmarkEnvironment"; // BenchmarkEnvironment is a base that can be implemented to used to perform global initialization and teardown // for a module class BenchmarkEnvironmentBase { public: virtual ~BenchmarkEnvironmentBase() = default; virtual void SetUpBenchmark() { } virtual void TearDownBenchmark() { } }; class BenchmarkEnvironmentRegistry { public: BenchmarkEnvironmentRegistry() = default; BenchmarkEnvironmentRegistry(const BenchmarkEnvironmentRegistry&) = delete; BenchmarkEnvironmentRegistry& operator=(const BenchmarkEnvironmentRegistry&) = delete; void AddBenchmarkEnvironment(std::unique_ptr env) { m_envs.push_back(std::move(env)); } std::vector>& GetBenchmarkEnvironments() { return m_envs; } private: std::vector> m_envs; }; /* * Creates a BenchmarkEnvironment using the specified template type and registers it with the BenchmarkEnvironmentRegister * @param T template argument that must have BenchmarkEnvironmentBase as a base class * @return returns a reference to the created BenchmarkEnvironment */ template T& RegisterBenchmarkEnvironment() { static_assert(std::is_base_of::value, "Supplied benchmark environment must be derived from BenchmarkEnvironmentBase"); static AZ::EnvironmentVariable s_benchmarkRegistry; if (!s_benchmarkRegistry) { s_benchmarkRegistry = AZ::Environment::CreateVariable(s_benchmarkEnvironmentName); } auto benchmarkEnv{ new T }; s_benchmarkRegistry->AddBenchmarkEnvironment(std::unique_ptr{ benchmarkEnv }); return *benchmarkEnv; } template std::array RegisterBenchmarkEnvironments() { constexpr size_t EnvironmentCount{ sizeof...(Ts) }; if constexpr (EnvironmentCount) { std::array benchmarkEnvs{ { &RegisterBenchmarkEnvironment()... } }; return benchmarkEnvs; } else { std::array benchmarkEnvs{}; return benchmarkEnvs; } } #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //! listener class to capture and print test output for embedded platforms class OutputEventListener : public ::testing::EmptyTestEventListener { public: std::list resultList; void OnTestEnd(const ::testing::TestInfo& test_info) override { std::string result; if (test_info.result()->Failed()) { result = "Fail"; } else { result = "Pass"; } std::string formattedResult = "[GTEST][" + result + "] " + test_info.test_case_name() + " " + test_info.name() + "\n"; resultList.emplace_back(formattedResult); } void OnTestProgramEnd(const ::testing::UnitTest& unit_test) override { for (std::string testResults : resultList) { AZ_Printf("", testResults.c_str()); } if (unit_test.current_test_info()) { AZ_Printf("", "[GTEST] %s completed %u tests with u% failed test cases.", unit_test.current_test_info()->name(), unit_test.total_test_count(), unit_test.failed_test_case_count()); } } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ITestEnvironment* DefaultTestEnv(); } // Test } // AZ #define AZ_UNIT_TEST_HOOK_NAME AzRunUnitTests #if !defined(AZ_MONOLITHIC_BUILD) // Environments should be declared dynamically, framework will handle deletion of resources #define AZ_UNIT_TEST_HOOK_ENV(TEST_ENV) \ AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv) \ { \ ::testing::InitGoogleMock(&argc, argv); \ if (AZ_TRAIT_AZTEST_ATTACH_RESULT_LISTENER) \ { \ ::testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); \ listeners.Append(new AZ::Test::OutputEventListener); \ } \ AZ::Test::excludeIntegTests(); \ AZ::Test::ApplyGlobalParameters(&argc, argv); \ AZ::Test::printUnusedParametersWarning(argc, argv); \ AZ::Test::addTestEnvironments({TEST_ENV}); \ int result = RUN_ALL_TESTS(); \ return result; \ } #if defined(HAVE_BENCHMARK) #define AZ_BENCHMARK_HOOK_ENV(TEST_ENV) \ AZTEST_EXPORT int AzRunBenchmarks(int argc, char** argv) \ { \ AZ::Test::RegisterBenchmarkEnvironments(); \ auto benchmarkEnvRegistry = AZ::Environment::FindVariable(AZ::Test::s_benchmarkEnvironmentName); \ std::vector>* benchmarkEnvs = benchmarkEnvRegistry ? &(benchmarkEnvRegistry->GetBenchmarkEnvironments()) : nullptr; \ if (benchmarkEnvs != nullptr) \ { \ for (std::unique_ptr& benchmarkEnv : *benchmarkEnvs) \ { \ if (benchmarkEnv) \ { \ benchmarkEnv->SetUpBenchmark(); \ } \ }\ } \ ::benchmark::Initialize(&argc, argv); \ ::benchmark::RunSpecifiedBenchmarks(); \ if (benchmarkEnvs != nullptr) \ { \ for (auto benchmarkEnvIter = benchmarkEnvs->rbegin(); benchmarkEnvIter != benchmarkEnvs->rend(); ++benchmarkEnvIter) \ { \ std::unique_ptr& benchmarkEnv = *benchmarkEnvIter; \ if (benchmarkEnv) \ { \ benchmarkEnv->TearDownBenchmark(); \ } \ }\ } \ return 0; \ } #define AZ_BENCHMARK_HOOK() \ AZTEST_EXPORT int AzRunBenchmarks(int argc, char** argv) \ { \ AZ::Test::RegisterBenchmarkEnvironments<>(); \ auto benchmarkEnvRegistry = AZ::Environment::FindVariable(AZ::Test::s_benchmarkEnvironmentName); \ std::vector>* benchmarkEnvs = benchmarkEnvRegistry ? &(benchmarkEnvRegistry->GetBenchmarkEnvironments()) : nullptr; \ if (benchmarkEnvs != nullptr) \ { \ for (std::unique_ptr& benchmarkEnv : *benchmarkEnvs) \ { \ if (benchmarkEnv) \ { \ benchmarkEnv->SetUpBenchmark(); \ } \ }\ } \ ::benchmark::Initialize(&argc, argv); \ ::benchmark::RunSpecifiedBenchmarks(); \ if (benchmarkEnvs != nullptr) \ { \ for (auto benchmarkEnvIter = benchmarkEnvs->rbegin(); benchmarkEnvIter != benchmarkEnvs->rend(); ++benchmarkEnvIter) \ { \ std::unique_ptr& benchmarkEnv = *benchmarkEnvIter; \ if (benchmarkEnv) \ { \ benchmarkEnv->TearDownBenchmark(); \ } \ }\ } \ return 0; \ } #else // !HAVE_BENCHMARK #define AZ_BENCHMARK_HOOK_ENV(TEST_ENV) \ int AzRunBenchmarks(int argc, char** argv) \ { \ std::cerr << "'AzRunBenchmarks' Not supported" << std::endl; \ return 1; \ } #define AZ_BENCHMARK_HOOK() \ int AzRunBenchmarks(int argc, char** argv) \ { \ std::cerr << "'AzRunBenchmarks' Not supported" << std::endl; \ return 1; \ } #endif // HAVE_BENCHMARK #if defined(AZ_TEST_EXECUTABLE) #define IMPLEMENT_TEST_EXECUTABLE_MAIN() \ int main(int argc, char** argv) \ { \ const bool isUnitTestCmd = (argc<2)?true:(strcmp(argv[1], AZ_STRINGIZE(AZ_UNIT_TEST_HOOK_NAME))==0); \ const bool isBenchmarkCmd = (argc<2)?false:(strcmp(argv[1], "AzRunBenchmarks")==0); \ if (isUnitTestCmd) \ { \ if (argc<2) \ { \ return AZ_UNIT_TEST_HOOK_NAME(argc, argv); \ } \ else \ { \ argv[1] = argv[0]; \ return AZ_UNIT_TEST_HOOK_NAME(argc-1, &argv[1]); \ } \ } \ else if (isBenchmarkCmd) \ { \ argv[1] = argv[0]; \ return AzRunBenchmarks(argc-1, &argv[1]); \ } \ else \ { \ std::cerr << "Invalid arguments for test" << std::endl; \ return 1; \ } \ } #else #define IMPLEMENT_TEST_EXECUTABLE_MAIN() #endif // defined(AZ_TEST_EXECUTABLE) #else // monolithic build #undef GTEST_MODULE_NAME_ #define GTEST_MODULE_NAME_ AZ_MODULE_NAME #define AZTEST_CONCAT_(a, b) a ## _ ## b #define AZTEST_CONCAT(a, b) AZTEST_CONCAT_(a, b) #define AZ_UNIT_TEST_HOOK_REGISTRY_NAME AZTEST_CONCAT(AZ_UNIT_TEST_HOOK_NAME, Registry) #define AZ_UNIT_TEST_HOOK_ENV(TEST_ENV) \ static AZ::Test::TestEnvironmentRegistry* AZ_UNIT_TEST_HOOK_REGISTRY_NAME =\ new( AZ_OS_MALLOC(sizeof(AZ::Test::TestEnvironmentRegistry), \ alignof(AZ::Test::TestEnvironmentRegistry))) \ AZ::Test::TestEnvironmentRegistry({ TEST_ENV }, AZ_MODULE_NAME, true); #define AZ_BENCHMARK_HOOK_ENV(TEST_ENV) #define AZ_BENCHMARK_HOOK() #endif // AZ_MONOLITHIC_BUILD // These macros are needed to implement unit test hooks necessary for running AzUnitTests or AzBenchmarks. // /* For unit test modules that implement AzUnitTests and AzBenchmarkTests with either a custom environment for AzUnitTests or a custom environment class for AzBenchmarks the follow use the overloaded 'IMPLEMENT_AZ_UNIT_TEST_HOOKS' macro AZ_UNIT_TEST_HOOK(UNIT_TEST_ENV, BENCHMARK_ENV_CLASS) // Implement unit test hooks without a custom AzUnitTest or AzBenchmark environment, AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); // Implement unit test hooks with a custom AzUnitTest environment AZ_UNIT_TEST_HOOK(new CustomEnvClass()); // Implement unit test hooks with a custom AzBenchmark environment class only AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV, CustomBenchmarkEnvClass); // Implement unit test hooks with a custom AzUnitTest environment and a custom AzBenchmark environment class AZ_UNIT_TEST_HOOK(new CustomEnvClass(), CustomBenchmarkEnvClass); */ #define DEFAULT_UNIT_TEST_ENV AZ::Test::DefaultTestEnv() #define AZ_UNIT_TEST_HOOK_1(_1) \ AZ_UNIT_TEST_HOOK_ENV(_1) \ AZ_BENCHMARK_HOOK() \ IMPLEMENT_TEST_EXECUTABLE_MAIN() #define AZ_UNIT_TEST_HOOK_2(_1, _2) \ AZ_UNIT_TEST_HOOK_ENV(_1) \ AZ_BENCHMARK_HOOK_ENV(_2) \ IMPLEMENT_TEST_EXECUTABLE_MAIN() #define AZ_UNIT_TEST_HOOK(...) AZ_MACRO_SPECIALIZE(AZ_UNIT_TEST_HOOK_, AZ_VA_NUM_ARGS(__VA_ARGS__), (__VA_ARGS__)) // Declares a visible external symbol which identifies an executable as containing tests #define DECLARE_AZ_UNIT_TEST_MAIN() AZTEST_EXPORT int ContainsAzUnitTestMain() { return 1; } // Attempts to invoke the unit test main function if appropriate flags are present, // otherwise simply continues launch as normal. #define INVOKE_AZ_UNIT_TEST_MAIN(...) \ do { \ AZ::Test::AzUnitTestMain unitTestMain({__VA_ARGS__}); \ if (unitTestMain.Run(argc, argv)) \ { \ return unitTestMain.ReturnCode(); \ } \ } while (0); // safe multi-line macro - creates a single statement // Some implementations use a commandLine rather than argc/argv #define INVOKE_AZ_UNIT_TEST_MAIN_COMMAND_LINE(...) \ do { \ AZ::Test::AzUnitTestMain unitTestMain({__VA_ARGS__}); \ if (unitTestMain.Run(commandLine)) \ { \ return unitTestMain.ReturnCode(); \ } \ } while (0); // safe multi-line macro - creates a single statement // Avoid problems with new/delete when AZ allocators are not ready or properly un/initialized. #define AZ_TEST_CLASS_ALLOCATOR(Class_) \ void* operator new (size_t size) \ { \ return AZ_OS_MALLOC(size, AZStd::alignment_of::value); \ } \ void operator delete(void* ptr) \ { \ AZ_OS_FREE(ptr); \ }