7e10267dfd
* Added single process isolation support to execute tests * Address review comments * Update README * Removed requirement of explicit call to clear method * Added macros for simplified usage * Updated tests to use process isolation framework * Adjust summary output format for isolated tests * Updated rccl_wrap tests * Used process isolation in AllocTests * Used process isolation and fixed failing tests * Modified test output, added signal handling Updated macros to handle lambdas * Convert argcheck tests to isolated tests * Convert proxy tests to isolated tests * Remove non-supported test * Fixed file descriptor handling and clearing env vars for tests
366 baris
15 KiB
C++
366 baris
15 KiB
C++
/*************************************************************************
|
|
* Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
|
|
*
|
|
* See LICENSE.txt for license information
|
|
************************************************************************/
|
|
#pragma once
|
|
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace RcclUnitTesting
|
|
{
|
|
|
|
/**
|
|
* @brief Generic thread-safe process isolated test runner
|
|
*
|
|
* This class provides a framework for running tests in isolated processes
|
|
* with clean environment settings and sequential execution.
|
|
*
|
|
*/
|
|
class ProcessIsolatedTestRunner
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Test execution result structure
|
|
*/
|
|
struct TestResult
|
|
{
|
|
std::string testName; ///< Name of the test
|
|
bool passed; ///< Whether the test passed
|
|
bool skipped; ///< Whether the test skipped
|
|
int exitCode; ///< Process exit code
|
|
pid_t processId; ///< Process ID that ran the test
|
|
std::chrono::milliseconds duration; ///< Test execution duration
|
|
std::string errorMessage; ///< Error message if test failed
|
|
std::unordered_map<std::string, std::string> environment; ///< Environment variables used
|
|
|
|
/**
|
|
* @brief Default constructor
|
|
*/
|
|
TestResult();
|
|
};
|
|
|
|
/**
|
|
* @brief Test configuration structure
|
|
*/
|
|
struct TestConfig
|
|
{
|
|
std::string name; ///< Test name
|
|
std::function<void()> testLogic; ///< Test function to execute
|
|
std::unordered_map<std::string, std::string>
|
|
environmentVariables; ///< Environment variables to set
|
|
std::chrono::seconds timeout; ///< Test timeout
|
|
bool inheritParentEnv; ///< Whether to inherit parent environment
|
|
std::vector<std::string> clearEnvVars; ///< Environment variables to explicitly clear
|
|
|
|
/**
|
|
* @brief Constructor
|
|
* @param testName Name of the test
|
|
* @param logic Test function to execute
|
|
*/
|
|
TestConfig(const std::string& testName, std::function<void()> logic);
|
|
|
|
/**
|
|
* @brief Set environment variables for this test
|
|
* @param env Map of environment variable name-value pairs
|
|
* @return Reference to this TestConfig for method chaining
|
|
*/
|
|
TestConfig& withEnvironment(const std::unordered_map<std::string, std::string>& env);
|
|
|
|
/**
|
|
* @brief Set timeout for this test
|
|
* @param timeoutSeconds Timeout in seconds
|
|
* @return Reference to this TestConfig for method chaining
|
|
*/
|
|
TestConfig& withTimeout(std::chrono::seconds timeoutSeconds);
|
|
|
|
/**
|
|
* @brief Configure environment inheritance
|
|
* @param inherit Whether to inherit parent environment variables
|
|
* @return Reference to this TestConfig for method chaining
|
|
*/
|
|
TestConfig& withCleanEnvironment(bool inherit = false);
|
|
|
|
/**
|
|
* @brief Clear a specific environment variable
|
|
* @param varName Name of the variable to clear
|
|
* @return Reference to this TestConfig for method chaining
|
|
*/
|
|
TestConfig& clearVariable(const std::string& varName);
|
|
|
|
/**
|
|
* @brief Set a specific environment variable
|
|
* @param name Variable name
|
|
* @param value Variable value
|
|
* @return Reference to this TestConfig for method chaining
|
|
*/
|
|
TestConfig& setVariable(const std::string& name, const std::string& value);
|
|
};
|
|
|
|
/**
|
|
* @brief Execution options for test runner
|
|
*/
|
|
struct ExecutionOptions
|
|
{
|
|
bool stopOnFirstFailure; ///< Stop execution on first test failure
|
|
bool verboseLogging; ///< Enable verbose logging
|
|
|
|
/**
|
|
* @brief Default constructor with sensible defaults
|
|
*/
|
|
ExecutionOptions();
|
|
};
|
|
|
|
private:
|
|
/**
|
|
* @brief Structure to hold captured process output
|
|
*/
|
|
struct CapturedOutput
|
|
{
|
|
std::string stdoutContent; ///< Captured stdout content
|
|
std::string stderrContent; ///< Captured stderr content
|
|
};
|
|
|
|
// Thread-safe static members for test management
|
|
static std::mutex testConfigsMutex_;
|
|
static std::vector<TestConfig> testConfigs_;
|
|
static std::mutex resultsMutex_;
|
|
static std::vector<TestResult> testResults_;
|
|
|
|
/**
|
|
* @brief Apply environment variables to current process
|
|
* @param config Test configuration containing environment settings
|
|
*/
|
|
static void applyEnvironmentVariables(const TestConfig& config);
|
|
|
|
/**
|
|
* @brief Execute a single test in the child process
|
|
* @param config Test configuration
|
|
* @return Exit code (0 for success, non-zero for failure)
|
|
*/
|
|
static int runTestInProcess(const TestConfig& config);
|
|
|
|
/**
|
|
* @brief Create pipes for capturing process output
|
|
* @param stdoutPipe Array to hold stdout pipe file descriptors [read, write]
|
|
* @param stderrPipe Array to hold stderr pipe file descriptors [read, write]
|
|
* @return True if pipes were created successfully, false otherwise
|
|
*/
|
|
static bool createOutputPipes(int stdoutPipe[2], int stderrPipe[2]);
|
|
|
|
/**
|
|
* @brief Redirect child process output to pipes
|
|
* @param stdoutPipe Stdout pipe file descriptors [read, write]
|
|
* @param stderrPipe Stderr pipe file descriptors [read, write]
|
|
*/
|
|
static void redirectOutputToPipes(int stdoutPipe[2], int stderrPipe[2]);
|
|
|
|
/**
|
|
* @brief Capture output from child process via pipes
|
|
* @param stdoutPipe Stdout pipe file descriptors [read, write]
|
|
* @param stderrPipe Stderr pipe file descriptors [read, write]
|
|
* @param pid Child process ID to monitor
|
|
* @param status Pointer to status variable for waitpid
|
|
* @return Captured output from stdout and stderr
|
|
*/
|
|
static CapturedOutput
|
|
captureProcessOutput(int stdoutPipe[2], int stderrPipe[2], pid_t pid, int* status);
|
|
|
|
/**
|
|
* @brief Display captured output with formatted delimiters
|
|
* @param output Captured output to display
|
|
* @param testName Name of the test for context
|
|
*/
|
|
static void displayCapturedOutput(const CapturedOutput& output, const std::string& testName);
|
|
|
|
public:
|
|
/**
|
|
* @brief Register a test configuration
|
|
* @param config Complete test configuration
|
|
*/
|
|
static void registerTest(const TestConfig& config);
|
|
|
|
/**
|
|
* @brief Register a simple test with just name and logic
|
|
* @param name Test name
|
|
* @param testLogic Test function to execute
|
|
*/
|
|
static void registerTest(const std::string& name, std::function<void()> testLogic);
|
|
|
|
/**
|
|
* @brief Register a test with environment variables
|
|
* @param name Test name
|
|
* @param testLogic Test function to execute
|
|
* @param env Environment variables to set for this test
|
|
*/
|
|
static void registerTest(
|
|
const std::string& name,
|
|
std::function<void()> testLogic,
|
|
const std::unordered_map<std::string, std::string>& env
|
|
);
|
|
|
|
/**
|
|
* @brief Record a test result (thread-safe)
|
|
* @param result Test result to record
|
|
*/
|
|
static void recordTestResult(const TestResult& result);
|
|
|
|
/**
|
|
* @brief Execute all registered tests sequentially
|
|
* @param options Execution options (defaults to continue on failure)
|
|
* @return True if all tests passed, false otherwise
|
|
* @note This method automatically clears all test registrations and results
|
|
* after execution, ensuring a clean state for the next test suite.
|
|
*/
|
|
static bool executeAllTests(const ExecutionOptions& options = ExecutionOptions());
|
|
|
|
/**
|
|
* @brief Generate and display test report
|
|
* @param options Execution options used for the test run
|
|
* @return True if all tests passed, false otherwise
|
|
*/
|
|
static bool generateReport(const ExecutionOptions& options);
|
|
|
|
/**
|
|
* @brief Get detailed test results (thread-safe)
|
|
* @return Vector of all test results
|
|
*/
|
|
static std::vector<TestResult> getTestResults();
|
|
|
|
/**
|
|
* @brief Clear test registry and results (thread-safe)
|
|
* @note Calling this method manually is typically not necessary, as
|
|
* executeAllTests() automatically clears registrations after execution.
|
|
* This method is primarily useful for advanced use cases or when tests
|
|
* are registered but not executed.
|
|
*/
|
|
static void clear();
|
|
|
|
/**
|
|
* @brief Get number of registered tests
|
|
* @return Number of registered tests
|
|
*/
|
|
static size_t getTestCount();
|
|
};
|
|
|
|
// Macros for Simplified Usage
|
|
|
|
/**
|
|
* @brief Register and execute a single isolated test with minimal boilerplate
|
|
*
|
|
* Uses variadic macros to automatically handle commas in lambda bodies
|
|
*
|
|
* @param test_name Name of the test (string)
|
|
* @param ... Lambda containing test logic (variadic to handle internal commas)
|
|
*
|
|
* Example:
|
|
* RUN_ISOLATED_TEST("MyTest", []() {
|
|
* EXPECT_TRUE(someFunction());
|
|
* });
|
|
*/
|
|
#define RUN_ISOLATED_TEST(test_name, ...) \
|
|
do \
|
|
{ \
|
|
::RcclUnitTesting::ProcessIsolatedTestRunner::registerTest(test_name, __VA_ARGS__); \
|
|
bool passed_ = ::RcclUnitTesting::ProcessIsolatedTestRunner::executeAllTests(); \
|
|
EXPECT_TRUE(passed_) << "Isolated test '" << test_name << "' failed"; \
|
|
} \
|
|
while(0)
|
|
|
|
/**
|
|
* @brief Register and execute a single isolated test with environment variables
|
|
*
|
|
* Uses variadic macros to automatically handle environment variable initializer lists
|
|
*
|
|
* @param test_name Name of the test (string)
|
|
* @param test_body Lambda containing test logic
|
|
* @param ... Environment variables as initializer list
|
|
*
|
|
* Example:
|
|
* RUN_ISOLATED_TEST_WITH_ENV("MyTest",
|
|
* []() { EXPECT_TRUE(someFunction()); },
|
|
* {{"VAR1", "value1"}, {"VAR2", "value2"}});
|
|
*
|
|
* Note: Uses __VA_ARGS__ to capture environment variables, which automatically
|
|
* handles commas in the initializer list without requiring extra parentheses.
|
|
*/
|
|
#define RUN_ISOLATED_TEST_WITH_ENV(test_name, test_body, ...) \
|
|
do \
|
|
{ \
|
|
::RcclUnitTesting::ProcessIsolatedTestRunner::registerTest( \
|
|
test_name, \
|
|
test_body, \
|
|
__VA_ARGS__ \
|
|
); \
|
|
bool passed_ = ::RcclUnitTesting::ProcessIsolatedTestRunner::executeAllTests(); \
|
|
EXPECT_TRUE(passed_) << "Isolated test '" << test_name << "' failed"; \
|
|
} \
|
|
while(0)
|
|
|
|
/**
|
|
* @brief Register and execute multiple isolated tests with default options
|
|
*
|
|
* This macro takes multiple TestConfig objects and executes them all.
|
|
* Tests are automatically cleaned up after execution.
|
|
*
|
|
* Example:
|
|
* RUN_ISOLATED_TESTS(
|
|
* ProcessIsolatedTestRunner::TestConfig("Test1", []() { ... }),
|
|
* ProcessIsolatedTestRunner::TestConfig("Test2", []() { ... })
|
|
* .withEnvironment({{"VAR", "value"}}),
|
|
* ProcessIsolatedTestRunner::TestConfig("Test3", []() { ... })
|
|
* .withTimeout(std::chrono::seconds(60))
|
|
* );
|
|
*/
|
|
#define RUN_ISOLATED_TESTS(...) \
|
|
do \
|
|
{ \
|
|
::RcclUnitTesting::ProcessIsolatedTestRunner::TestConfig configs_[] = {__VA_ARGS__}; \
|
|
for(const auto& config_ : configs_) \
|
|
{ \
|
|
::RcclUnitTesting::ProcessIsolatedTestRunner::registerTest(config_); \
|
|
} \
|
|
bool passed_ = ::RcclUnitTesting::ProcessIsolatedTestRunner::executeAllTests(); \
|
|
EXPECT_TRUE(passed_) << "One or more isolated tests failed"; \
|
|
} \
|
|
while(0)
|
|
|
|
/**
|
|
* @brief Register and execute multiple isolated tests with custom options
|
|
*
|
|
* This macro takes execution options and multiple TestConfig objects.
|
|
*
|
|
* Example:
|
|
* ProcessIsolatedTestRunner::ExecutionOptions opts;
|
|
* opts.stopOnFirstFailure = true;
|
|
* opts.verboseLogging = true;
|
|
*
|
|
* RUN_ISOLATED_TESTS_WITH_OPTIONS(opts,
|
|
* ProcessIsolatedTestRunner::TestConfig("Test1", []() { ... }),
|
|
* ProcessIsolatedTestRunner::TestConfig("Test2", []() { ... })
|
|
* );
|
|
*/
|
|
#define RUN_ISOLATED_TESTS_WITH_OPTIONS(options, ...) \
|
|
do \
|
|
{ \
|
|
::RcclUnitTesting::ProcessIsolatedTestRunner::TestConfig configs_[] = {__VA_ARGS__}; \
|
|
for(const auto& config_ : configs_) \
|
|
{ \
|
|
::RcclUnitTesting::ProcessIsolatedTestRunner::registerTest(config_); \
|
|
} \
|
|
bool passed_ = ::RcclUnitTesting::ProcessIsolatedTestRunner::executeAllTests(options); \
|
|
EXPECT_TRUE(passed_) << "One or more isolated tests failed"; \
|
|
} \
|
|
while(0)
|
|
|
|
} // namespace RcclUnitTesting
|