Merge pull request #40 from aws-lumberyard-dev/TIF/Jenkins

Refactor ProcessScheduler with Execute method
This commit is contained in:
jonawals
2021-05-24 14:16:20 +01:00
committed by GitHub
12 changed files with 274 additions and 209 deletions
@@ -50,32 +50,29 @@ namespace TestImpact
{
public:
//! Constructs the job runner with the specified parameters to constrain job runs.
//! @param maxConcurrentProcesses he maximum number of concurrent jobs in-flight.
explicit JobRunner(size_t maxConcurrentProcesses);
//! Executes the specified jobs and returns the products of their labor.
//! @param jobs The arguments (and other pertinent information) required for each job to be run.
//! @param stdOutRouting The standard output routing to be specified for all jobs.
//! @param stdErrRouting The standard error routing to be specified for all jobs.
//! @param maxConcurrentProcesses he maximum number of concurrent jobs in-flight.
//! @param processTimeout The maximum duration a job may be in-flight before being forcefully terminated (nullopt if no timeout).
//! @param scheduleTimeout The maximum duration the scheduler may run before forcefully terminating all in-flight jobs (nullopt if
//! no timeout).
JobRunner(
//! @param jobTimeout The maximum duration a job may be in-flight before being forcefully terminated (nullopt if no timeout).
//! @param runnerTimeout The maximum duration the scheduler may run before forcefully terminating all in-flight jobs (nullopt if no timeout).
//! @param payloadMapProducer The client callback to be called when all jobs have finished to transform the work produced by each job into the desired output.
//! @param jobCallback The client callback to be called when each job changes state.
//! @return The result of the run sequence and the jobs with their associated payloads.
AZStd::pair<ProcessSchedulerResult, AZStd::vector<typename JobT>> Execute(
const AZStd::vector<typename JobT::Info>& jobs,
PayloadMapProducer<JobT> payloadMapProducer,
StdOutputRouting stdOutRouting,
StdErrorRouting stdErrRouting,
size_t maxConcurrentProcesses,
AZStd::optional<AZStd::chrono::milliseconds> processTimeout,
AZStd::optional<AZStd::chrono::milliseconds> scheduleTimeout);
//! Executes the specified jobs and returns the products of their labor.
//! @note: the job and payload callbacks are specified here rather than in the constructor to allow clients to use capturing lambdas
//! should they desire to.
//! @param jobs The arguments (and other pertinent information) required for each job to be run.
//! @param jobCallback The client callback to be called when each job changes state.
//! @param payloadMapProducer The client callback to be called when all jobs have finished to transform the work produced by each
//! job into the desired output.
AZStd::vector<typename JobT> Execute(
const AZStd::vector<typename JobT::Info>& jobs, JobCallback<typename JobT> jobCallback,
PayloadMapProducer<JobT> payloadMapProducer);
AZStd::optional<AZStd::chrono::milliseconds> jobTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
JobCallback<typename JobT> jobCallback);
private:
size_t m_maxConcurrentProcesses = 0; //!< Maximum number of concurrent jobs being executed at a given time.
ProcessScheduler m_processScheduler;
StdOutputRouting m_stdOutRouting; //!< Standard output routing from each job process to job runner.
StdErrorRouting m_stdErrRouting; //!< Standard error routing from each job process to job runner
AZStd::optional<AZStd::chrono::milliseconds> m_jobTimeout; //!< Maximum time a job can run for before being forcefully terminated.
@@ -83,25 +80,20 @@ namespace TestImpact
};
template<typename JobT>
JobRunner<JobT>::JobRunner(
StdOutputRouting stdOutRouting,
StdErrorRouting stdErrRouting,
size_t maxConcurrentProcesses,
AZStd::optional<AZStd::chrono::milliseconds> jobTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout)
: m_maxConcurrentProcesses(maxConcurrentProcesses)
, m_stdOutRouting(stdOutRouting)
, m_stdErrRouting(stdErrRouting)
, m_jobTimeout(jobTimeout)
, m_runnerTimeout(runnerTimeout)
JobRunner<JobT>::JobRunner(size_t maxConcurrentProcesses)
: m_processScheduler(maxConcurrentProcesses)
{
}
template<typename JobT>
AZStd::vector<JobT> JobRunner<JobT>::Execute(
AZStd::pair<ProcessSchedulerResult, AZStd::vector<typename JobT>> JobRunner<JobT>::Execute(
const AZStd::vector<typename JobT::Info>& jobInfos,
JobCallback<JobT> jobCallback,
PayloadMapProducer<JobT> payloadMapProducer)
PayloadMapProducer<JobT> payloadMapProducer,
StdOutputRouting stdOutRouting,
StdErrorRouting stdErrRouting,
AZStd::optional<AZStd::chrono::milliseconds> jobTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
JobCallback<typename JobT> jobCallback)
{
AZStd::vector<ProcessInfo> processes;
AZStd::unordered_map<JobT::Info::IdType, AZStd::pair<JobMeta, const typename JobT::Info*>> metas;
@@ -115,11 +107,11 @@ namespace TestImpact
const auto* jobInfo = &jobInfos[jobIndex];
const auto jobId = jobInfo->GetId().m_value;
metas.emplace(jobId, AZStd::pair<JobMeta, const typename JobT::Info*>{JobMeta{}, jobInfo});
processes.emplace_back(jobId, m_stdOutRouting, m_stdErrRouting, jobInfo->GetCommand().m_args);
processes.emplace_back(jobId, stdOutRouting, stdErrRouting, jobInfo->GetCommand().m_args);
}
// Wrapper around low-level process launch callback to gather job meta-data and present a simplified callback interface to the client
const auto processLaunchCallback = [&jobCallback, &jobInfos, &metas](
const ProcessLaunchCallback processLaunchCallback = [&jobCallback, &jobInfos, &metas](
TestImpact::ProcessId pid,
TestImpact::LaunchResult launchResult,
AZStd::chrono::high_resolution_clock::time_point createTime)
@@ -138,7 +130,7 @@ namespace TestImpact
};
// Wrapper around low-level process exit callback to gather job meta-data and present a simplified callback interface to the client
const auto processExitCallback = [&jobCallback, &jobInfos, &metas](
const ProcessExitCallback processExitCallback = [&jobCallback, &jobInfos, &metas](
TestImpact::ProcessId pid,
TestImpact::ExitCondition exitCondition,
TestImpact::ReturnCode returnCode,
@@ -169,14 +161,12 @@ namespace TestImpact
};
// Schedule all jobs for execution
ProcessScheduler scheduler(
const auto result = m_processScheduler.Execute(
processes,
jobTimeout,
runnerTimeout,
processLaunchCallback,
processExitCallback,
m_maxConcurrentProcesses,
m_jobTimeout,
m_runnerTimeout
);
processExitCallback);
// Hand off the jobs to the client for payload generation
auto payloadMap = payloadMapProducer(metas);
@@ -188,6 +178,6 @@ namespace TestImpact
jobs.emplace_back(JobT(jobInfo, AZStd::move(metas.at(jobId).first), AZStd::move(payloadMap[jobId])));
}
return jobs;
return { result, jobs };
}
} // namespace TestImpact
@@ -18,7 +18,7 @@
namespace TestImpact
{
struct ProcessScheduler::ProcessInFlight
struct ProcessInFlight
{
AZStd::unique_ptr<Process> m_process;
AZStd::optional<AZStd::chrono::high_resolution_clock::time_point> m_startTime;
@@ -26,29 +26,64 @@ namespace TestImpact
AZStd::string m_stdError;
};
ProcessScheduler::ProcessScheduler(
const AZStd::vector<ProcessInfo>& processes,
const ProcessLaunchCallback& processLaunchCallback,
const ProcessExitCallback& processExitCallback,
class ProcessScheduler::ExecutionState
{
public:
ExecutionState(
size_t maxConcurrentProcesses,
AZStd::optional<AZStd::chrono::milliseconds> processTimeout,
AZStd::optional<AZStd::chrono::milliseconds> scheduleTimeout,
ProcessLaunchCallback& processLaunchCallback,
ProcessExitCallback& processExitCallback);
~ExecutionState();
ProcessSchedulerResult MonitorProcesses(const AZStd::vector<ProcessInfo>& processes);
void TerminateAllProcesses(ExitCondition exitStatus);
private:
ProcessCallbackResult PopAndLaunch(ProcessInFlight& processInFlight);
StdContent ConsumeProcessStdContent(ProcessInFlight& processInFlight);
void AccumulateProcessStdContent(ProcessInFlight& processInFlight);
size_t m_maxConcurrentProcesses = 0;
ProcessLaunchCallback m_processLaunchCallback;
ProcessExitCallback m_processExitCallback;
AZStd::optional<AZStd::chrono::milliseconds> m_processTimeout;
AZStd::optional<AZStd::chrono::milliseconds> m_scheduleTimeout;
AZStd::chrono::high_resolution_clock::time_point m_startTime;
AZStd::vector<ProcessInFlight> m_processPool;
AZStd::queue<ProcessInfo> m_processQueue;
};
ProcessScheduler::ExecutionState::ExecutionState(
size_t maxConcurrentProcesses,
AZStd::optional<AZStd::chrono::milliseconds> processTimeout,
AZStd::optional<AZStd::chrono::milliseconds> scheduleTimeout)
: m_processCreateCallback(processLaunchCallback)
AZStd::optional<AZStd::chrono::milliseconds> scheduleTimeout,
ProcessLaunchCallback& processLaunchCallback,
ProcessExitCallback& processExitCallback)
: m_maxConcurrentProcesses(maxConcurrentProcesses)
, m_processLaunchCallback(processLaunchCallback)
, m_processExitCallback(processExitCallback)
, m_processTimeout(processTimeout)
, m_scheduleTimeout(scheduleTimeout)
, m_startTime(AZStd::chrono::high_resolution_clock::now())
{
AZ_TestImpact_Eval(maxConcurrentProcesses != 0, ProcessException, "Max Number of concurrent processes in flight cannot be 0");
AZ_TestImpact_Eval(!processes.empty(), ProcessException, "Number of processes to launch cannot be 0");
AZ_TestImpact_Eval(
!m_processTimeout.has_value() || m_processTimeout->count() > 0, ProcessException,
"Process timeout must be empty or non-zero value");
AZ_TestImpact_Eval(
!m_scheduleTimeout.has_value() || m_scheduleTimeout->count() > 0, ProcessException,
"Scheduler timeout must be empty or non-zero value");
}
const size_t numConcurrentProcesses = AZStd::min(processes.size(), maxConcurrentProcesses);
ProcessScheduler::ExecutionState::~ExecutionState()
{
TerminateAllProcesses(ExitCondition::Terminated);
}
ProcessSchedulerResult ProcessScheduler::ExecutionState::MonitorProcesses(const AZStd::vector<ProcessInfo>& processes)
{
AZ_TestImpact_Eval(!processes.empty(), ProcessException, "Number of processes to launch cannot be 0");
m_startTime = AZStd::chrono::high_resolution_clock::now();
const size_t numConcurrentProcesses = AZStd::min(processes.size(), m_maxConcurrentProcesses);
m_processPool.resize(numConcurrentProcesses);
for (const auto& process : processes)
@@ -60,21 +95,12 @@ namespace TestImpact
{
if (PopAndLaunch(process) == ProcessCallbackResult::Abort)
{
// Client chose to abort the scheduler
TerminateAllProcesses(ExitCondition::Terminated);
return;
return ProcessSchedulerResult::UserAborted;
}
}
MonitorProcesses();
}
ProcessScheduler::~ProcessScheduler()
{
TerminateAllProcesses(ExitCondition::Terminated);
}
void ProcessScheduler::MonitorProcesses()
{
while (true)
{
// Check to see whether or not the scheduling has exceeded its specified runtime
@@ -86,7 +112,7 @@ namespace TestImpact
{
// Runtime exceeded, terminate all proccesses and schedule no further
TerminateAllProcesses(ExitCondition::Timeout);
return;
return ProcessSchedulerResult::Timeout;
}
}
@@ -98,7 +124,7 @@ namespace TestImpact
{
if (processInFlight.m_process)
{
// Process is alive (note: not necessarilly currently running)
// Process is alive (note: not necessarily currently running)
AccumulateProcessStdContent(processInFlight);
const ProcessId processId = processInFlight.m_process->GetProcessInfo().GetId();
@@ -119,7 +145,7 @@ namespace TestImpact
{
// Client chose to abort the scheduler
TerminateAllProcesses(ExitCondition::Terminated);
return;
return ProcessSchedulerResult::UserAborted;
}
else if (!m_processQueue.empty())
{
@@ -128,7 +154,7 @@ namespace TestImpact
{
// Client chose to abort the scheduler
TerminateAllProcesses(ExitCondition::Terminated);
return;
return ProcessSchedulerResult::UserAborted;
}
else
{
@@ -157,9 +183,9 @@ namespace TestImpact
ConsumeProcessStdContent(processInFlight),
exitTime))
{
// Flight time exceeded, terminate this process
// Client chose to abort the scheduler
TerminateAllProcesses(ExitCondition::Terminated);
return;
return ProcessSchedulerResult::UserAborted;
}
}
@@ -176,7 +202,7 @@ namespace TestImpact
{
// Client chose to abort the scheduler
TerminateAllProcesses(ExitCondition::Terminated);
return;
return ProcessSchedulerResult::UserAborted;
}
else
{
@@ -192,9 +218,11 @@ namespace TestImpact
break;
}
}
return ProcessSchedulerResult::Graceful;
}
ProcessCallbackResult ProcessScheduler::PopAndLaunch(ProcessInFlight& processInFlight)
ProcessCallbackResult ProcessScheduler::ExecutionState::PopAndLaunch(ProcessInFlight& processInFlight)
{
auto processInfo = m_processQueue.front();
m_processQueue.pop();
@@ -212,17 +240,17 @@ namespace TestImpact
createResult = LaunchResult::Failure;
}
return m_processCreateCallback(processInfo.GetId(), createResult, createTime);
return m_processLaunchCallback(processInfo.GetId(), createResult, createTime);
}
void ProcessScheduler::AccumulateProcessStdContent(ProcessInFlight& processInFlight)
void ProcessScheduler::ExecutionState::AccumulateProcessStdContent(ProcessInFlight& processInFlight)
{
// Accumulate the stdout/stderr so we don't deadlock with the process waiting for the pipe to empty before finishing
processInFlight.m_stdOutput += processInFlight.m_process->ConsumeStdOut().value_or("");
processInFlight.m_stdError += processInFlight.m_process->ConsumeStdErr().value_or("");
}
StdContent ProcessScheduler::ConsumeProcessStdContent(ProcessInFlight& processInFlight)
StdContent ProcessScheduler::ExecutionState::ConsumeProcessStdContent(ProcessInFlight& processInFlight)
{
return
{
@@ -235,7 +263,7 @@ namespace TestImpact
};
}
void ProcessScheduler::TerminateAllProcesses(ExitCondition exitStatus)
void ProcessScheduler::ExecutionState::TerminateAllProcesses(ExitCondition exitStatus)
{
bool isCallingBackToClient = true;
const ReturnCode returnCode = static_cast<ReturnCode>(exitStatus);
@@ -267,4 +295,27 @@ namespace TestImpact
}
}
}
ProcessScheduler::ProcessScheduler(size_t maxConcurrentProcesses)
: m_maxConcurrentProcesses(maxConcurrentProcesses)
{
AZ_TestImpact_Eval(maxConcurrentProcesses != 0, ProcessException, "Max Number of concurrent processes in flight cannot be 0");
}
ProcessScheduler::~ProcessScheduler() = default;
ProcessSchedulerResult ProcessScheduler::Execute(
const AZStd::vector<ProcessInfo>& processes,
AZStd::optional<AZStd::chrono::milliseconds> processTimeout,
AZStd::optional<AZStd::chrono::milliseconds> scheduleTimeout,
ProcessLaunchCallback processLaunchCallback,
ProcessExitCallback processExitCallback)
{
AZ_TestImpact_Eval(!m_executionState, ProcessException, "Couldn't execute schedule, schedule already in progress");
m_executionState = AZStd::make_unique<ExecutionState>(
m_maxConcurrentProcesses, processTimeout, scheduleTimeout, processLaunchCallback, processExitCallback);
const auto result = m_executionState->MonitorProcesses(processes);
m_executionState.reset();
return result;
}
} // namespace TestImpact
@@ -22,6 +22,7 @@
#include <AzCore/std/functional.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace TestImpact
{
@@ -49,6 +50,14 @@ namespace TestImpact
Abort //!< Abort scheduling immediately.
};
//! Result of the process scheduling sequence.
enum class ProcessSchedulerResult : AZ::u8
{
Graceful, //!< The scheduler completed its run without incident or was terminated gracefully in response to a client callback result.
UserAborted, //!< The scheduler aborted prematurely due to the user returning an abort value from thier callback handler.
Timeout //!< The scheduler aborted its run prematurely due to its runtime exceeding the scheduler timeout value.
};
//! Callback for process launch attempt.
//! @param processId The id of the process that attempted to launch.
//! @param launchResult The result of the process launch attempt.
@@ -79,38 +88,28 @@ namespace TestImpact
{
public:
//! Constructs the scheduler with the specified batch of processes.
//! @param processes The batch of processes to schedule.
//! @param processLaunchCallback The process launch callback function.
//! @param processExitCallback The process exit callback function.
//! @param maxConcurrentProcesses The maximum number of concurrent processes in-flight.
//! @param processTimeout The maximum duration a process may be in-flight for before being forcefully terminated.
//! @param scheduleTimeout The maximum duration the scheduler may run before forcefully terminating all in-flight processes.
//! processes and abandoning any queued processes.
ProcessScheduler(
const AZStd::vector<ProcessInfo>& processes,
const ProcessLaunchCallback& processLaunchCallback,
const ProcessExitCallback& processExitCallback,
size_t maxConcurrentProcesses,
AZStd::optional<AZStd::chrono::milliseconds> processTimeout,
AZStd::optional<AZStd::chrono::milliseconds> scheduleTimeout);
explicit ProcessScheduler(size_t maxConcurrentProcesses);
~ProcessScheduler();
//! Executes the specified processes and calls the client callbacks (if any) as each process progresses in its life cycle.
//! @note Multiple subsequent calls to Execute are permitted.
//! @param processes The batch of processes to schedule.
//! @param processTimeout The maximum duration a process may be in-flight for before being forcefully terminated.
//! @param scheduleTimeout The maximum duration the scheduler may run before forcefully terminating all in-flight processes.
//! @param processLaunchCallback The process launch callback function.
//! @param processExitCallback The process exit callback function.
//! @returns The state that triggered the end of the schedule sequence.
ProcessSchedulerResult Execute(
const AZStd::vector<ProcessInfo>& processes,
AZStd::optional<AZStd::chrono::milliseconds> processTimeout,
AZStd::optional<AZStd::chrono::milliseconds> scheduleTimeout,
ProcessLaunchCallback processLaunchCallback,
ProcessExitCallback processExitCallback);
private:
struct ProcessInFlight;
void MonitorProcesses();
ProcessCallbackResult PopAndLaunch(ProcessInFlight& processInFlight);
void TerminateAllProcesses(ExitCondition exitStatus);
StdContent ConsumeProcessStdContent(ProcessInFlight& processInFlight);
void AccumulateProcessStdContent(ProcessInFlight& processInFlight);
const ProcessLaunchCallback m_processCreateCallback;
const ProcessExitCallback m_processExitCallback;
const AZStd::optional<AZStd::chrono::milliseconds> m_processTimeout;
const AZStd::optional<AZStd::chrono::milliseconds> m_scheduleTimeout;
const AZStd::chrono::high_resolution_clock::time_point m_startTime;
AZStd::vector<ProcessInFlight> m_processPool;
AZStd::queue<ProcessInfo> m_processQueue;
class ExecutionState;
AZStd::unique_ptr<ExecutionState> m_executionState;
size_t m_maxConcurrentProcesses = 0;
};
} // namespace TestImpact
@@ -106,19 +106,18 @@ namespace TestImpact
return m_cache;
}
TestEnumerator::TestEnumerator(
AZStd::optional<ClientJobCallback> clientCallback,
size_t maxConcurrentEnumerations,
AZStd::optional<AZStd::chrono::milliseconds> enumerationTimeout,
AZStd::optional<AZStd::chrono::milliseconds> enumeratorTimeout)
: JobRunner(clientCallback, AZStd::nullopt, StdOutputRouting::None, StdErrorRouting::None, maxConcurrentEnumerations, enumerationTimeout, enumeratorTimeout)
TestEnumerator::TestEnumerator(size_t maxConcurrentEnumerations)
: JobRunner(maxConcurrentEnumerations)
{
}
AZStd::vector<TestEnumerator::Job> TestEnumerator::Enumerate(
AZStd::pair<ProcessSchedulerResult, AZStd::vector<TestEnumerator::Job>> TestEnumerator::Enumerate(
const AZStd::vector<JobInfo>& jobInfos,
CacheExceptionPolicy cacheExceptionPolicy,
JobExceptionPolicy jobExceptionPolicy)
JobExceptionPolicy jobExceptionPolicy,
AZStd::optional<AZStd::chrono::milliseconds> enumerationTimeout,
AZStd::optional<AZStd::chrono::milliseconds> enumeratorTimeout,
AZStd::optional<ClientJobCallback> clientCallback)
{
AZStd::vector<Job> cachedJobs;
AZStd::vector<JobInfo> jobQueue;
@@ -179,14 +178,23 @@ namespace TestImpact
};
// Generate the enumeration results for the jobs that weren't cached
auto jobs = ExecuteJobs(jobQueue, payloadGenerator, jobExceptionPolicy);
auto [result, jobs] = ExecuteJobs(
jobQueue,
jobExceptionPolicy,
payloadGenerator,
StdOutputRouting::None,
StdErrorRouting::None,
enumerationTimeout,
enumeratorTimeout,
clientCallback,
AZStd::nullopt);
// We need to add the cached jobs to the completed job list even though they technically weren't executed
for (auto&& job : cachedJobs)
{
jobs.emplace_back(AZStd::move(job));
}
return jobs;
return { result, jobs };
}
} // namespace TestImpact
@@ -73,22 +73,23 @@ namespace TestImpact
using CacheExceptionPolicy = Bitwise::CacheExceptionPolicy;
//! Constructs a test enumerator with the specified parameters common to all enumeration job runs of this enumerator.
//! @param clientCallback The optional client callback to be called whenever an enumeration job changes state.
//! @param maxConcurrentEnumerations The maximum number of enumerations to be in flight at any given time.
//! @param enumerationTimeout The maximum duration an enumeration may be in-flight for before being forcefully terminated.
//! @param enumeratorTimeout The maximum duration the enumerator may run before forcefully terminating all in-flight enumerations.
TestEnumerator(
AZStd::optional<ClientJobCallback> clientCallback,
size_t maxConcurrentEnumerations,
AZStd::optional<AZStd::chrono::milliseconds> enumerationTimeout,
AZStd::optional<AZStd::chrono::milliseconds> enumeratorTimeout);
explicit TestEnumerator(size_t maxConcurrentEnumerations);
//! Executes the specified test enumeration jobs according to the specified cache and job exception policies.
//! @param jobInfos The enumeration jobs to execute.
//! @param cacheExceptionPolicy The cache exception policy to be used for this run.
//! @param jobExceptionPolicy The enumeration job exception policy to be used for this run.
//! @return the test enumeration jobs with their associated test enumeration payloads.
AZStd::vector<Job> Enumerate(
const AZStd::vector<JobInfo>& jobInfos, CacheExceptionPolicy cacheExceptionPolicy, JobExceptionPolicy jobExceptionPolicy);
//! @param enumerationTimeout The maximum duration an enumeration may be in-flight for before being forcefully terminated.
//! @param enumeratorTimeout The maximum duration the enumerator may run before forcefully terminating all in-flight enumerations.
//! @param clientCallback The optional client callback to be called whenever an enumeration job changes state.
//! @return The result of the run sequence and the enumeration jobs with their associated test enumeration payloads.
AZStd::pair<ProcessSchedulerResult, AZStd::vector<Job>> Enumerate(
const AZStd::vector<JobInfo>& jobInfos,
CacheExceptionPolicy cacheExceptionPolicy,
JobExceptionPolicy jobExceptionPolicy,
AZStd::optional<AZStd::chrono::milliseconds> enumerationTimeout,
AZStd::optional<AZStd::chrono::milliseconds> enumeratorTimeout,
AZStd::optional<ClientJobCallback> clientCallback);
};
} // namespace TestImpact
@@ -53,32 +53,31 @@ namespace TestImpact
using JobDataMap = JobDataMap<Job>;
//! Constructs the job runner with the specified parameters common to all job runs of this runner.
//! @param clientCallback The optional callback function provided by the client to be called upon job state change.
//! @param clientCallback The optional callback function provided by the derived job runner to be called upon job state change.
//! @param stdOutRouting The standard output routing from the underlying job processes to the derived runner.
//! @param stdErrorRouting The standard error routing from the underlying job processes to the derived runner.
//! @param maxConcurrentJobs The maximum number of jobs to be in flight at any given time.
//! @param jobTimeout The maximum duration a job may be in-flight for before being forcefully terminated (nullopt if no timeout).
//! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight jobs (nullopt if no
//! timeout).
TestJobRunner(
AZStd::optional<ClientJobCallback> clientCallback,
AZStd::optional<DerivedJobCallback> derivedJobCallback,
StdOutputRouting stdOutRouting,
StdErrorRouting stdErrRouting,
size_t maxConcurrentJobs,
AZStd::optional<AZStd::chrono::milliseconds> jobTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout);
explicit TestJobRunner(size_t maxConcurrentJobs);
protected:
//! Runs the specified jobs and returns the completed payloads produced by each job.
//! @param jobInfos The batch of jobs to execute.
//! @param payloadMapProducer The client callback for producing the payload map based on the completed job data.
//! @param jobExceptionPolicy The job execution policy for this job run.
AZStd::vector<Job> ExecuteJobs(
//! @param payloadMapProducer The client callback for producing the payload map based on the completed job data.
//! @param stdOutRouting The standard output routing from the underlying job processes to the derived runner.
//! @param stdErrorRouting The standard error routing from the underlying job processes to the derived runner.
//! @param jobTimeout The maximum duration a job may be in-flight for before being forcefully terminated (nullopt if no timeout).
//! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight jobs (nullopt if no timeout).
//! @param clientCallback The optional callback function provided by the client to be called upon job state change.
//! @param clientCallback The optional callback function provided by the derived job runner to be called upon job state change.
//! @returns The result of the run sequence and the jobs that the sequence produced.
AZStd::pair<ProcessSchedulerResult, AZStd::vector<Job>> ExecuteJobs(
const AZStd::vector<JobInfo>& jobInfos,
JobExceptionPolicy jobExceptionPolicy,
PayloadMapProducer<Job> payloadMapProducer,
JobExceptionPolicy jobExceptionPolicy);
StdOutputRouting stdOutRouting,
StdErrorRouting stdErrRouting,
AZStd::optional<AZStd::chrono::milliseconds> jobTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
AZStd::optional<ClientJobCallback> clientCallback,
AZStd::optional<DerivedJobCallback> derivedJobCallback);
const AZStd::optional<ClientJobCallback> m_clientJobCallback;
@@ -88,28 +87,25 @@ namespace TestImpact
};
template<typename Data, typename Payload>
TestJobRunner<Data, Payload>::TestJobRunner(
AZStd::optional<ClientJobCallback> clientCallback,
AZStd::optional<DerivedJobCallback> derivedJobCallback,
StdOutputRouting stdOutRouting,
StdErrorRouting stdErrRouting,
size_t maxConcurrentJobs,
AZStd::optional<AZStd::chrono::milliseconds> jobTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout)
: m_jobRunner(stdOutRouting, stdErrRouting, maxConcurrentJobs, jobTimeout, runnerTimeout)
, m_clientJobCallback(clientCallback)
, m_derivedJobCallback(derivedJobCallback)
TestJobRunner<Data, Payload>::TestJobRunner(size_t maxConcurrentJobs)
: m_jobRunner(maxConcurrentJobs)
{
}
template<typename Data, typename Payload>
AZStd::vector<typename TestJobRunner<Data, Payload>::Job> TestJobRunner<Data, Payload>::ExecuteJobs(
AZStd::pair<ProcessSchedulerResult, AZStd::vector<typename TestJobRunner<Data, Payload>::Job>> TestJobRunner<Data, Payload>::ExecuteJobs(
const AZStd::vector<JobInfo>& jobInfos,
JobExceptionPolicy jobExceptionPolicy,
PayloadMapProducer<Job> payloadMapProducer,
JobExceptionPolicy jobExceptionPolicy)
StdOutputRouting stdOutRouting,
StdErrorRouting stdErrRouting,
AZStd::optional<AZStd::chrono::milliseconds> jobTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
AZStd::optional<ClientJobCallback> clientCallback,
AZStd::optional<DerivedJobCallback> derivedJobCallback)
{
// Callback to handle job exception policies and client/derived callbacks
const auto jobCallback = [this, &jobExceptionPolicy](const JobInfo& jobInfo, const JobMeta& meta, StdContent&& std)
const auto jobCallback = [&clientCallback, &derivedJobCallback, &jobExceptionPolicy](const JobInfo& jobInfo, const JobMeta& meta, StdContent&& std)
{
auto callbackResult = ProcessCallbackResult::Continue;
if (meta.m_result == JobResult::FailedToExecute && IsFlagSet(jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute))
@@ -121,23 +117,23 @@ namespace TestImpact
callbackResult = ProcessCallbackResult::Abort;
}
if (m_derivedJobCallback.has_value())
if (derivedJobCallback.has_value())
{
if (const auto result = (*m_derivedJobCallback)(jobInfo, meta, AZStd::move(std));
if (const auto result = (*derivedJobCallback)(jobInfo, meta, AZStd::move(std));
result == ProcessCallbackResult::Abort)
{
callbackResult = ProcessCallbackResult::Abort;
}
}
if (m_clientJobCallback.has_value())
if (clientCallback.has_value())
{
(*m_clientJobCallback)(jobInfo, meta);
(*clientCallback)(jobInfo, meta);
}
return callbackResult;
};
return m_jobRunner.Execute(jobInfos, jobCallback, payloadMapProducer);
return m_jobRunner.Execute(jobInfos, payloadMapProducer, stdOutRouting, stdErrRouting, jobTimeout, runnerTimeout, jobCallback);
}
} // namespace TestImpact
@@ -52,19 +52,18 @@ namespace TestImpact
return {AZStd::move(run), AZStd::move(coverage)};
}
InstrumentedTestRunner::InstrumentedTestRunner(
AZStd::optional<ClientJobCallback> clientCallback,
size_t maxConcurrentRuns,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout)
: JobRunner(clientCallback, AZStd::nullopt, StdOutputRouting::None, StdErrorRouting::None, maxConcurrentRuns, runTimeout, runnerTimeout)
InstrumentedTestRunner::InstrumentedTestRunner(size_t maxConcurrentRuns)
: JobRunner(maxConcurrentRuns)
{
}
AZStd::vector<InstrumentedTestRunner::Job> InstrumentedTestRunner::RunInstrumentedTests(
AZStd::pair<ProcessSchedulerResult, AZStd::vector<InstrumentedTestRunner::Job>> InstrumentedTestRunner::RunInstrumentedTests(
const AZStd::vector<JobInfo>& jobInfos,
CoverageExceptionPolicy coverageExceptionPolicy,
JobExceptionPolicy jobExceptionPolicy)
JobExceptionPolicy jobExceptionPolicy,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
AZStd::optional<ClientJobCallback> clientCallback)
{
const auto payloadGenerator = [this, coverageExceptionPolicy](const JobDataMap& jobDataMap)
{
@@ -98,6 +97,15 @@ namespace TestImpact
return runs;
};
return ExecuteJobs(jobInfos, payloadGenerator, jobExceptionPolicy);
return ExecuteJobs(
jobInfos,
jobExceptionPolicy,
payloadGenerator,
StdOutputRouting::None,
StdErrorRouting::None,
runTimeout,
runnerTimeout,
clientCallback,
AZStd::nullopt);
}
} // namespace TestImpact
@@ -53,21 +53,24 @@ namespace TestImpact
using CoverageExceptionPolicy = Bitwise::CoverageExceptionPolicy;
//! Constructs an instrumented test runner with the specified parameters common to all job runs of this runner.
//! @param clientCallback The optional client callback to be called whenever a run job changes state.
//! @param maxConcurrentRuns The maximum number of runs to be in flight at any given time.
//! @param runTimeout The maximum duration a run may be in-flight for before being forcefully terminated.
//! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight runs.
InstrumentedTestRunner(
AZStd::optional<ClientJobCallback> clientCallback, size_t maxConcurrentRuns,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout, AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout);
//! @param maxConcurrentRuns The maximum number of runs to be in flight at any given time.
explicit InstrumentedTestRunner(size_t maxConcurrentRuns);
//! Executes the specified instrumented test run jobs according to the specified job exception policies.
//! @param jobInfos The test run jobs to execute.
//! @param CoverageExceptionPolicy The coverage exception policy to be used for this run.
//! @param jobExceptionPolicy The test run job exception policy to be used for this run (use
//! TestJobExceptionPolicy::OnFailedToExecute to throw on test failures).
//! @return the instrumented test run jobs with their associated test run and test coverage payloads.
AZStd::vector<Job> RunInstrumentedTests(
const AZStd::vector<JobInfo>& jobInfos, CoverageExceptionPolicy coverageExceptionPolicy, JobExceptionPolicy jobExceptionPolicy);
//! @param runTimeout The maximum duration a run may be in-flight for before being forcefully terminated.
//! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight runs.
//! @param clientCallback The optional client callback to be called whenever a run job changes state.
//! @return The result of the run sequence and the instrumented run jobs with their associated test run and coverage payloads.
AZStd::pair<ProcessSchedulerResult, AZStd::vector<Job>> RunInstrumentedTests(
const AZStd::vector<JobInfo>& jobInfos,
CoverageExceptionPolicy coverageExceptionPolicy,
JobExceptionPolicy jobExceptionPolicy,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
AZStd::optional<ClientJobCallback> clientCallback);
};
} // namespace TestImpact
@@ -103,7 +103,7 @@ namespace TestImpact
return m_modules.size();
}
const AZStd::vector<AZStd::string>& TestCoverage::GetSourcesCovered() const
const AZStd::vector<RepoPath>& TestCoverage::GetSourcesCovered() const
{
return m_sourcesCovered;
}
@@ -44,7 +44,7 @@ namespace TestImpact
size_t GetNumModulesCovered() const;
//! Returns the sorted set of unique sources covered (empty if no coverage).
const AZStd::vector<AZStd::string>& GetSourcesCovered() const;
const AZStd::vector<RepoPath>& GetSourcesCovered() const;
//! Returns the modules covered (empty if no coverage).
const AZStd::vector<ModuleCoverage>& GetModuleCoverages() const;
@@ -56,7 +56,7 @@ namespace TestImpact
void CalculateTestMetrics();
AZStd::vector<ModuleCoverage> m_modules;
AZStd::vector<AZStd::string> m_sourcesCovered;
AZStd::vector<RepoPath> m_sourcesCovered;
AZStd::optional<CoverageLevel> m_coverageLevel;
};
} // namespace TestImpact
@@ -26,18 +26,17 @@ namespace TestImpact
return TestRun(GTest::TestRunSuitesFactory(ReadFileContents<TestRunException>(runFile)), duration);
}
TestRunner::TestRunner(
AZStd::optional<ClientJobCallback> clientCallback,
size_t maxConcurrentRuns,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout)
: JobRunner(clientCallback, AZStd::nullopt, StdOutputRouting::None, StdErrorRouting::None, maxConcurrentRuns, runTimeout, runnerTimeout)
TestRunner::TestRunner(size_t maxConcurrentRuns)
: JobRunner(maxConcurrentRuns)
{
}
AZStd::vector<TestRunner::Job> TestRunner::RunTests(
AZStd::pair<ProcessSchedulerResult, AZStd::vector<TestRunner::Job>> TestRunner::RunTests(
const AZStd::vector<JobInfo>& jobInfos,
JobExceptionPolicy jobExceptionPolicy)
JobExceptionPolicy jobExceptionPolicy,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
AZStd::optional<ClientJobCallback> clientCallback)
{
const auto payloadGenerator = [this](const JobDataMap& jobDataMap)
{
@@ -62,6 +61,15 @@ namespace TestImpact
return runs;
};
return ExecuteJobs(jobInfos, payloadGenerator, jobExceptionPolicy);
return ExecuteJobs(
jobInfos,
jobExceptionPolicy,
payloadGenerator,
StdOutputRouting::None,
StdErrorRouting::None,
runTimeout,
runnerTimeout,
clientCallback,
AZStd::nullopt);
}
} // namespace TestImpact
@@ -26,21 +26,22 @@ namespace TestImpact
public:
//! Constructs a test runner with the specified parameters common to all job runs of this runner.
//! @param clientCallback The optional client callback to be called whenever a run job changes state.
//! @param maxConcurrentRuns The maximum number of runs to be in flight at any given time.
//! @param runTimeout The maximum duration a run may be in-flight for before being forcefully terminated.
//! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight runs.
TestRunner(
AZStd::optional<ClientJobCallback> clientCallback,
size_t maxConcurrentRuns,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout);
explicit TestRunner(size_t maxConcurrentRuns);
//! Executes the specified test run jobs according to the specified job exception policies.
//! @param jobInfos The test run jobs to execute.
//! @param jobExceptionPolicy The test run job exception policy to be used for this run (use
//! TestJobExceptionPolicy::OnFailedToExecute to throw on test failures).
//! @return the test run jobs with their associated test run payloads.
AZStd::vector<Job> RunTests(const AZStd::vector<JobInfo>& jobInfos, JobExceptionPolicy jobExceptionPolicy);
//! @param runTimeout The maximum duration a run may be in-flight for before being forcefully terminated.
//! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight runs.
//! @param clientCallback The optional client callback to be called whenever a run job changes state.
//! @return The result of the run sequence and the run jobs with their associated test run payloads.
AZStd::pair<ProcessSchedulerResult, AZStd::vector<Job>> RunTests(
const AZStd::vector<JobInfo>& jobInfos,
JobExceptionPolicy jobExceptionPolicy,
AZStd::optional<AZStd::chrono::milliseconds> runTimeout,
AZStd::optional<AZStd::chrono::milliseconds> runnerTimeout,
AZStd::optional<ClientJobCallback> clientCallback);
};
} // namespace TestImpact