forked from qt-creator/qt-creator
TaskTree: Introduce WaitFor, Condition and ConditionActivator
WaitFor(condition) Group element enables postponing Group's children execution until the condition is met. Use ConditionActivator::activate() method to signal that the condition is met. A call to ConditionActivator::activate() schedules a request to the TaskTree instructing it to resume execution of awaiting condition. The Group containing the WaitFor element is started itself, and its setup handler is being called. If setup handler returned TaskAction::Continue, the children execution is being postponed. Otherwise, when StopWithDone or StopWithError is returned, the Group finishes and WaitFor element is no-op in this context. This functionality is going to be used when some part of the task tree may continue only after some data has been collected, and data collection took place not from inside start or done handlers. The example is running debugger for already started process - the debugger may run after the process already started, delivered its PID and it's still running. In this way we may start a debugger process in parallel in right point of time. This patch implements the 5th point inside QTCREATORBUG-28741. Task-number: QTCREATORBUG-28741 Change-Id: I4afaedb0b34fe0383c16a6d1f74bf07f74cc088a Reviewed-by: Christian Kandeler <christian.kandeler@qt.io> Reviewed-by: hjk <hjk@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -74,6 +74,54 @@ void TreeStorageBase::activateStorage(int id) const
|
|||||||
m_storageData->m_activeStorage = id;
|
m_storageData->m_activeStorage = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Condition::Condition()
|
||||||
|
: m_conditionData(new ConditionData()) {}
|
||||||
|
|
||||||
|
Condition::ConditionData::~ConditionData()
|
||||||
|
{
|
||||||
|
QTC_CHECK(m_activatorHash.isEmpty());
|
||||||
|
qDeleteAll(m_activatorHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionActivator *Condition::activator() const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_conditionData->m_activeActivator, return nullptr);
|
||||||
|
const auto it = m_conditionData->m_activatorHash.constFind(m_conditionData->m_activeActivator);
|
||||||
|
QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return nullptr);
|
||||||
|
return it.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Condition::createActivator(TaskNode *node) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_conditionData->m_activeActivator == 0, return 0); // TODO: should be allowed?
|
||||||
|
const int newId = ++m_conditionData->m_activatorCounter;
|
||||||
|
m_conditionData->m_activatorHash.insert(newId, new ConditionActivator(node));
|
||||||
|
return newId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Condition::deleteActivator(int id) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_conditionData->m_activeActivator == 0, return); // TODO: should be allowed?
|
||||||
|
const auto it = m_conditionData->m_activatorHash.constFind(id);
|
||||||
|
QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return);
|
||||||
|
delete it.value();
|
||||||
|
m_conditionData->m_activatorHash.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// passing 0 deactivates currently active condition
|
||||||
|
void Condition::activateActivator(int id) const
|
||||||
|
{
|
||||||
|
if (id == 0) {
|
||||||
|
QTC_ASSERT(m_conditionData->m_activeActivator, return);
|
||||||
|
m_conditionData->m_activeActivator = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QTC_ASSERT(m_conditionData->m_activeActivator == 0, return);
|
||||||
|
const auto it = m_conditionData->m_activatorHash.find(id);
|
||||||
|
QTC_ASSERT(it != m_conditionData->m_activatorHash.end(), return);
|
||||||
|
m_conditionData->m_activeActivator = id;
|
||||||
|
}
|
||||||
|
|
||||||
ParallelLimit sequential(1);
|
ParallelLimit sequential(1);
|
||||||
ParallelLimit parallel(0);
|
ParallelLimit parallel(0);
|
||||||
Workflow stopOnError(WorkflowPolicy::StopOnError);
|
Workflow stopOnError(WorkflowPolicy::StopOnError);
|
||||||
@@ -84,20 +132,21 @@ Workflow optional(WorkflowPolicy::Optional);
|
|||||||
|
|
||||||
void TaskItem::addChildren(const QList<TaskItem> &children)
|
void TaskItem::addChildren(const QList<TaskItem> &children)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return);
|
QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping...");
|
||||||
|
return);
|
||||||
for (const TaskItem &child : children) {
|
for (const TaskItem &child : children) {
|
||||||
switch (child.m_type) {
|
switch (child.m_type) {
|
||||||
case Type::Group:
|
case Type::Group:
|
||||||
m_children.append(child);
|
m_children.append(child);
|
||||||
break;
|
break;
|
||||||
case Type::Limit:
|
case Type::Limit:
|
||||||
QTC_ASSERT(m_type == Type::Group,
|
QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a "
|
||||||
qWarning("Mode may only be a child of Group, skipping..."); return);
|
"Group, skipping..."); return);
|
||||||
m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition?
|
m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition?
|
||||||
break;
|
break;
|
||||||
case Type::Policy:
|
case Type::Policy:
|
||||||
QTC_ASSERT(m_type == Type::Group,
|
QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a "
|
||||||
qWarning("Workflow Policy may only be a child of Group, skipping..."); return);
|
"Group, skipping..."); return);
|
||||||
m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition?
|
m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition?
|
||||||
break;
|
break;
|
||||||
case Type::TaskHandler:
|
case Type::TaskHandler:
|
||||||
@@ -109,7 +158,7 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
|
|||||||
break;
|
break;
|
||||||
case Type::GroupHandler:
|
case Type::GroupHandler:
|
||||||
QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
|
QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
|
||||||
"child of Group, skipping..."); break);
|
"child of a Group, skipping..."); break);
|
||||||
QTC_ASSERT(!child.m_groupHandler.m_setupHandler
|
QTC_ASSERT(!child.m_groupHandler.m_setupHandler
|
||||||
|| !m_groupHandler.m_setupHandler,
|
|| !m_groupHandler.m_setupHandler,
|
||||||
qWarning("Group Setup Handler redefinition, overriding..."));
|
qWarning("Group Setup Handler redefinition, overriding..."));
|
||||||
@@ -126,6 +175,12 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
|
|||||||
if (child.m_groupHandler.m_errorHandler)
|
if (child.m_groupHandler.m_errorHandler)
|
||||||
m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler;
|
m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler;
|
||||||
break;
|
break;
|
||||||
|
case Type::Condition:
|
||||||
|
QTC_ASSERT(m_type == Type::Group, qWarning("WaitFor may only be a child of a Group, "
|
||||||
|
"skipping..."); break);
|
||||||
|
QTC_ASSERT(!m_condition, qWarning("WaitFor redefinition, overriding..."));
|
||||||
|
m_condition = child.m_condition;
|
||||||
|
break;
|
||||||
case Type::Storage:
|
case Type::Storage:
|
||||||
m_storageList.append(child.m_storageList);
|
m_storageList.append(child.m_storageList);
|
||||||
break;
|
break;
|
||||||
@@ -140,147 +195,25 @@ using namespace Tasking;
|
|||||||
class TaskTreePrivate;
|
class TaskTreePrivate;
|
||||||
class TaskNode;
|
class TaskNode;
|
||||||
|
|
||||||
class TaskContainer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
|
||||||
TaskContainer *parentContainer)
|
|
||||||
: m_constData(taskTreePrivate, task, parentContainer, this) {}
|
|
||||||
TaskAction start();
|
|
||||||
TaskAction continueStart(TaskAction startAction, int nextChild);
|
|
||||||
TaskAction startChildren(int nextChild);
|
|
||||||
TaskAction childDone(bool success);
|
|
||||||
void stop();
|
|
||||||
void invokeEndHandler();
|
|
||||||
bool isRunning() const { return m_runtimeData.has_value(); }
|
|
||||||
bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
|
|
||||||
|
|
||||||
struct ConstData {
|
|
||||||
ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
|
||||||
TaskContainer *parentContainer, TaskContainer *thisContainer);
|
|
||||||
~ConstData() { qDeleteAll(m_children); }
|
|
||||||
TaskTreePrivate * const m_taskTreePrivate = nullptr;
|
|
||||||
TaskContainer * const m_parentContainer = nullptr;
|
|
||||||
|
|
||||||
const int m_parallelLimit = 1;
|
|
||||||
const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
|
||||||
const TaskItem::GroupHandler m_groupHandler;
|
|
||||||
const QList<TreeStorageBase> m_storageList;
|
|
||||||
const QList<TaskNode *> m_children;
|
|
||||||
const int m_taskCount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeData {
|
|
||||||
RuntimeData(const ConstData &constData);
|
|
||||||
~RuntimeData();
|
|
||||||
|
|
||||||
static QList<int> createStorages(const TaskContainer::ConstData &constData);
|
|
||||||
void callStorageDoneHandlers();
|
|
||||||
bool updateSuccessBit(bool success);
|
|
||||||
int currentLimit() const;
|
|
||||||
|
|
||||||
const ConstData &m_constData;
|
|
||||||
const QList<int> m_storageIdList;
|
|
||||||
int m_doneCount = 0;
|
|
||||||
bool m_successBit = true;
|
|
||||||
Guard m_startGuard;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConstData m_constData;
|
|
||||||
std::optional<RuntimeData> m_runtimeData;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TaskNode : public QObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
|
||||||
TaskContainer *parentContainer)
|
|
||||||
: m_taskHandler(task.taskHandler())
|
|
||||||
, m_container(taskTreePrivate, task, parentContainer)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
|
|
||||||
// in order to unwind properly.
|
|
||||||
TaskAction start();
|
|
||||||
void stop();
|
|
||||||
void invokeEndHandler(bool success);
|
|
||||||
bool isRunning() const { return m_task || m_container.isRunning(); }
|
|
||||||
bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; }
|
|
||||||
int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; }
|
|
||||||
TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
const TaskItem::TaskHandler m_taskHandler;
|
|
||||||
TaskContainer m_container;
|
|
||||||
std::unique_ptr<TaskInterface> m_task;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TaskTreePrivate
|
class TaskTreePrivate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TaskTreePrivate(TaskTree *taskTree)
|
TaskTreePrivate(TaskTree *taskTree)
|
||||||
: q(taskTree) {}
|
: q(taskTree) {}
|
||||||
|
|
||||||
void start() {
|
void start();
|
||||||
QTC_ASSERT(m_root, return);
|
void stop();
|
||||||
m_progressValue = 0;
|
void advanceProgress(int byValue);
|
||||||
emitStartedAndProgress();
|
void emitStartedAndProgress();
|
||||||
// TODO: check storage handlers for not existing storages in tree
|
void emitProgress();
|
||||||
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
|
void emitDone();
|
||||||
QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
|
void emitError();
|
||||||
"exist in task tree. Its handlers will never be called."));
|
bool addCondition(const TaskItem &task, TaskContainer *container);
|
||||||
}
|
void createConditionActivators();
|
||||||
m_root->start();
|
void deleteConditionActivators();
|
||||||
}
|
void activateConditions();
|
||||||
void stop() {
|
void deactivateConditions();
|
||||||
QTC_ASSERT(m_root, return);
|
QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages);
|
||||||
if (!m_root->isRunning())
|
|
||||||
return;
|
|
||||||
// TODO: should we have canceled flag (passed to handler)?
|
|
||||||
// Just one done handler with result flag:
|
|
||||||
// FinishedWithSuccess, FinishedWithError, Canceled, TimedOut.
|
|
||||||
// Canceled either directly by user, or by workflow policy - doesn't matter, in both
|
|
||||||
// cases canceled from outside.
|
|
||||||
m_root->stop();
|
|
||||||
emitError();
|
|
||||||
}
|
|
||||||
void advanceProgress(int byValue) {
|
|
||||||
if (byValue == 0)
|
|
||||||
return;
|
|
||||||
QTC_CHECK(byValue > 0);
|
|
||||||
QTC_CHECK(m_progressValue + byValue <= m_root->taskCount());
|
|
||||||
m_progressValue += byValue;
|
|
||||||
emitProgress();
|
|
||||||
}
|
|
||||||
void emitStartedAndProgress() {
|
|
||||||
GuardLocker locker(m_guard);
|
|
||||||
emit q->started();
|
|
||||||
emit q->progressValueChanged(m_progressValue);
|
|
||||||
}
|
|
||||||
void emitProgress() {
|
|
||||||
GuardLocker locker(m_guard);
|
|
||||||
emit q->progressValueChanged(m_progressValue);
|
|
||||||
}
|
|
||||||
void emitDone() {
|
|
||||||
QTC_CHECK(m_progressValue == m_root->taskCount());
|
|
||||||
GuardLocker locker(m_guard);
|
|
||||||
emit q->done();
|
|
||||||
}
|
|
||||||
void emitError() {
|
|
||||||
QTC_CHECK(m_progressValue == m_root->taskCount());
|
|
||||||
GuardLocker locker(m_guard);
|
|
||||||
emit q->errorOccurred();
|
|
||||||
}
|
|
||||||
QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages) {
|
|
||||||
QList<TreeStorageBase> addedStorages;
|
|
||||||
for (const TreeStorageBase &storage : storages) {
|
|
||||||
QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into "
|
|
||||||
"one TaskTree twice, skipping..."); continue);
|
|
||||||
addedStorages << storage;
|
|
||||||
m_storages << storage;
|
|
||||||
}
|
|
||||||
return addedStorages;
|
|
||||||
}
|
|
||||||
void callSetupHandler(TreeStorageBase storage, int storageId) {
|
void callSetupHandler(TreeStorageBase storage, int storageId) {
|
||||||
callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler);
|
callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler);
|
||||||
}
|
}
|
||||||
@@ -308,36 +241,252 @@ public:
|
|||||||
TaskTree *q = nullptr;
|
TaskTree *q = nullptr;
|
||||||
Guard m_guard;
|
Guard m_guard;
|
||||||
int m_progressValue = 0;
|
int m_progressValue = 0;
|
||||||
|
QHash<Condition, TaskContainer *> m_conditions;
|
||||||
QSet<TreeStorageBase> m_storages;
|
QSet<TreeStorageBase> m_storages;
|
||||||
QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
|
QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
|
||||||
std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
|
std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
|
||||||
};
|
};
|
||||||
|
|
||||||
class StorageActivator
|
class TaskContainer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StorageActivator(TaskContainer *container)
|
TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
||||||
: m_container(container) { activateStorages(m_container); }
|
TaskNode *parentNode, TaskContainer *parentContainer)
|
||||||
~StorageActivator() { deactivateStorages(m_container); }
|
: m_constData(taskTreePrivate, task, parentNode, parentContainer, this)
|
||||||
|
, m_conditionData(taskTreePrivate->addCondition(task, this)
|
||||||
|
? ConditionData() : std::optional<ConditionData>()) {}
|
||||||
|
TaskAction start();
|
||||||
|
TaskAction continueStart(TaskAction startAction, int nextChild);
|
||||||
|
TaskAction startChildren(int nextChild);
|
||||||
|
TaskAction childDone(bool success);
|
||||||
|
void activateCondition();
|
||||||
|
void stop();
|
||||||
|
void invokeEndHandler();
|
||||||
|
bool isRunning() const { return m_runtimeData.has_value(); }
|
||||||
|
bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
|
||||||
|
|
||||||
|
struct ConstData {
|
||||||
|
ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode,
|
||||||
|
TaskContainer *parentContainer, TaskContainer *thisContainer);
|
||||||
|
~ConstData() { qDeleteAll(m_children); }
|
||||||
|
TaskTreePrivate * const m_taskTreePrivate = nullptr;
|
||||||
|
TaskNode * const m_parentNode = nullptr;
|
||||||
|
TaskContainer * const m_parentContainer = nullptr;
|
||||||
|
|
||||||
|
const int m_parallelLimit = 1;
|
||||||
|
const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
||||||
|
const TaskItem::GroupHandler m_groupHandler;
|
||||||
|
const QList<TreeStorageBase> m_storageList;
|
||||||
|
const QList<TaskNode *> m_children;
|
||||||
|
const int m_taskCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConditionData {
|
||||||
|
bool m_activated = false;
|
||||||
|
int m_conditionId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeData {
|
||||||
|
RuntimeData(const ConstData &constData);
|
||||||
|
~RuntimeData();
|
||||||
|
|
||||||
|
static QList<int> createStorages(const TaskContainer::ConstData &constData);
|
||||||
|
void callStorageDoneHandlers();
|
||||||
|
bool updateSuccessBit(bool success);
|
||||||
|
int currentLimit() const;
|
||||||
|
|
||||||
|
const ConstData &m_constData;
|
||||||
|
const QList<int> m_storageIdList;
|
||||||
|
int m_doneCount = 0;
|
||||||
|
bool m_successBit = true;
|
||||||
|
Guard m_startGuard;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConstData m_constData;
|
||||||
|
std::optional<ConditionData> m_conditionData;
|
||||||
|
std::optional<RuntimeData> m_runtimeData;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskNode : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
||||||
|
TaskContainer *parentContainer)
|
||||||
|
: m_taskHandler(task.taskHandler())
|
||||||
|
, m_container(taskTreePrivate, task, this, parentContainer)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
|
||||||
|
// in order to unwind properly.
|
||||||
|
TaskAction start();
|
||||||
|
void stop();
|
||||||
|
void invokeEndHandler(bool success);
|
||||||
|
bool isRunning() const { return m_task || m_container.isRunning(); }
|
||||||
|
bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; }
|
||||||
|
int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; }
|
||||||
|
TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; }
|
||||||
|
void activateCondition();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void activateStorages(TaskContainer *container)
|
const TaskItem::TaskHandler m_taskHandler;
|
||||||
|
TaskContainer m_container;
|
||||||
|
std::unique_ptr<TaskInterface> m_task;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TaskTreePrivate::start()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_root, return);
|
||||||
|
m_progressValue = 0;
|
||||||
|
emitStartedAndProgress();
|
||||||
|
// TODO: check storage handlers for not existing storages in tree
|
||||||
|
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
|
||||||
|
QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
|
||||||
|
"exist in task tree. Its handlers will never be called."));
|
||||||
|
}
|
||||||
|
createConditionActivators();
|
||||||
|
m_root->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::stop()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_root, return);
|
||||||
|
if (!m_root->isRunning())
|
||||||
|
return;
|
||||||
|
// TODO: should we have canceled flag (passed to handler)?
|
||||||
|
// Just one done handler with result flag:
|
||||||
|
// FinishedWithSuccess, FinishedWithError, Canceled, TimedOut.
|
||||||
|
// Canceled either directly by user, or by workflow policy - doesn't matter, in both
|
||||||
|
// cases canceled from outside.
|
||||||
|
m_root->stop();
|
||||||
|
emitError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::advanceProgress(int byValue)
|
||||||
|
{
|
||||||
|
if (byValue == 0)
|
||||||
|
return;
|
||||||
|
QTC_CHECK(byValue > 0);
|
||||||
|
QTC_CHECK(m_progressValue + byValue <= m_root->taskCount());
|
||||||
|
m_progressValue += byValue;
|
||||||
|
emitProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::emitStartedAndProgress()
|
||||||
|
{
|
||||||
|
GuardLocker locker(m_guard);
|
||||||
|
emit q->started();
|
||||||
|
emit q->progressValueChanged(m_progressValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::emitProgress()
|
||||||
|
{
|
||||||
|
GuardLocker locker(m_guard);
|
||||||
|
emit q->progressValueChanged(m_progressValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::emitDone()
|
||||||
|
{
|
||||||
|
deleteConditionActivators();
|
||||||
|
QTC_CHECK(m_progressValue == m_root->taskCount());
|
||||||
|
GuardLocker locker(m_guard);
|
||||||
|
emit q->done();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::emitError()
|
||||||
|
{
|
||||||
|
deleteConditionActivators();
|
||||||
|
QTC_CHECK(m_progressValue == m_root->taskCount());
|
||||||
|
GuardLocker locker(m_guard);
|
||||||
|
emit q->errorOccurred();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskTreePrivate::addCondition(const TaskItem &task, TaskContainer *container)
|
||||||
|
{
|
||||||
|
if (!task.condition())
|
||||||
|
return false;
|
||||||
|
QTC_ASSERT(!m_conditions.contains(*task.condition()), qWarning("Can't add the same condition "
|
||||||
|
"into one TaskTree twice, skipping..."); return false);
|
||||||
|
m_conditions.insert(*task.condition(), container);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::createConditionActivators()
|
||||||
|
{
|
||||||
|
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
|
||||||
|
Condition condition = it.key();
|
||||||
|
TaskContainer *container = it.value();
|
||||||
|
container->m_conditionData->m_conditionId
|
||||||
|
= condition.createActivator(container->m_constData.m_parentNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::deleteConditionActivators()
|
||||||
|
{
|
||||||
|
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
|
||||||
|
Condition condition = it.key();
|
||||||
|
TaskContainer *container = it.value();
|
||||||
|
condition.deleteActivator(container->m_conditionData->m_conditionId);
|
||||||
|
container->m_conditionData = TaskContainer::ConditionData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::activateConditions()
|
||||||
|
{
|
||||||
|
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
|
||||||
|
Condition condition = it.key();
|
||||||
|
TaskContainer *container = it.value();
|
||||||
|
condition.activateActivator(container->m_conditionData->m_conditionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTreePrivate::deactivateConditions()
|
||||||
|
{
|
||||||
|
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it)
|
||||||
|
it.key().activateActivator(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<TreeStorageBase> TaskTreePrivate::addStorages(const QList<TreeStorageBase> &storages)
|
||||||
|
{
|
||||||
|
QList<TreeStorageBase> addedStorages;
|
||||||
|
for (const TreeStorageBase &storage : storages) {
|
||||||
|
QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into "
|
||||||
|
"one TaskTree twice, skipping..."); continue);
|
||||||
|
addedStorages << storage;
|
||||||
|
m_storages << storage;
|
||||||
|
}
|
||||||
|
return addedStorages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Activate/deactivate Conditions
|
||||||
|
class ExecutionContextActivator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExecutionContextActivator(TaskContainer *container)
|
||||||
|
: m_container(container) { activateContext(m_container); }
|
||||||
|
~ExecutionContextActivator() { deactivateContext(m_container); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void activateContext(TaskContainer *container)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(container && container->isRunning(), return);
|
QTC_ASSERT(container && container->isRunning(), return);
|
||||||
const TaskContainer::ConstData &constData = container->m_constData;
|
const TaskContainer::ConstData &constData = container->m_constData;
|
||||||
if (constData.m_parentContainer)
|
if (constData.m_parentContainer)
|
||||||
activateStorages(constData.m_parentContainer);
|
activateContext(constData.m_parentContainer);
|
||||||
|
else
|
||||||
|
constData.m_taskTreePrivate->activateConditions();
|
||||||
for (int i = 0; i < constData.m_storageList.size(); ++i)
|
for (int i = 0; i < constData.m_storageList.size(); ++i)
|
||||||
constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i));
|
constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i));
|
||||||
}
|
}
|
||||||
static void deactivateStorages(TaskContainer *container)
|
static void deactivateContext(TaskContainer *container)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(container && container->isRunning(), return);
|
QTC_ASSERT(container && container->isRunning(), return);
|
||||||
const TaskContainer::ConstData &constData = container->m_constData;
|
const TaskContainer::ConstData &constData = container->m_constData;
|
||||||
for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order
|
for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order
|
||||||
constData.m_storageList[i].activateStorage(0);
|
constData.m_storageList[i].activateStorage(0);
|
||||||
if (constData.m_parentContainer)
|
if (constData.m_parentContainer)
|
||||||
deactivateStorages(constData.m_parentContainer);
|
deactivateContext(constData.m_parentContainer);
|
||||||
|
else
|
||||||
|
constData.m_taskTreePrivate->deactivateConditions();
|
||||||
}
|
}
|
||||||
TaskContainer *m_container = nullptr;
|
TaskContainer *m_container = nullptr;
|
||||||
};
|
};
|
||||||
@@ -346,7 +495,7 @@ template <typename Handler, typename ...Args,
|
|||||||
typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
|
typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
|
||||||
ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args)
|
ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args)
|
||||||
{
|
{
|
||||||
StorageActivator activator(container);
|
ExecutionContextActivator activator(container);
|
||||||
GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
|
GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
|
||||||
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
|
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
@@ -362,8 +511,10 @@ static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
||||||
TaskContainer *parentContainer, TaskContainer *thisContainer)
|
TaskNode *parentNode, TaskContainer *parentContainer,
|
||||||
|
TaskContainer *thisContainer)
|
||||||
: m_taskTreePrivate(taskTreePrivate)
|
: m_taskTreePrivate(taskTreePrivate)
|
||||||
|
, m_parentNode(parentNode)
|
||||||
, m_parentContainer(parentContainer)
|
, m_parentContainer(parentContainer)
|
||||||
, m_parallelLimit(task.parallelLimit())
|
, m_parallelLimit(task.parallelLimit())
|
||||||
, m_workflowPolicy(task.workflowPolicy())
|
, m_workflowPolicy(task.workflowPolicy())
|
||||||
@@ -440,8 +591,12 @@ TaskAction TaskContainer::start()
|
|||||||
if (startAction != TaskAction::Continue)
|
if (startAction != TaskAction::Continue)
|
||||||
m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount);
|
m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount);
|
||||||
}
|
}
|
||||||
if (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue)
|
if (startAction == TaskAction::Continue) {
|
||||||
startAction = TaskAction::StopWithDone;
|
if (m_conditionData && !m_conditionData->m_activated) // Group has condition and it wasn't activated yet
|
||||||
|
return TaskAction::Continue;
|
||||||
|
if (m_constData.m_children.isEmpty())
|
||||||
|
startAction = TaskAction::StopWithDone;
|
||||||
|
}
|
||||||
return continueStart(startAction, 0);
|
return continueStart(startAction, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,6 +668,35 @@ TaskAction TaskContainer::childDone(bool success)
|
|||||||
return continueStart(startAction, limit);
|
return continueStart(startAction, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConditionActivator::activate()
|
||||||
|
{
|
||||||
|
m_node->activateCondition();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::activateCondition()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_conditionData, return);
|
||||||
|
if (!m_constData.m_taskTreePrivate->m_root->isRunning())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!isRunning())
|
||||||
|
return; // Condition not run yet or group already skipped or stopped
|
||||||
|
|
||||||
|
if (!m_conditionData->m_activated)
|
||||||
|
return; // May it happen that scheduled call is coming from previous TaskTree's start?
|
||||||
|
|
||||||
|
if (m_runtimeData->m_doneCount != 0)
|
||||||
|
return; // In meantime the group was started
|
||||||
|
|
||||||
|
for (TaskNode *child : m_constData.m_children) {
|
||||||
|
if (child->isRunning())
|
||||||
|
return; // In meantime the group was started
|
||||||
|
}
|
||||||
|
const TaskAction startAction = m_constData.m_children.isEmpty() ? TaskAction::StopWithDone
|
||||||
|
: TaskAction::Continue;
|
||||||
|
continueStart(startAction, 0);
|
||||||
|
}
|
||||||
|
|
||||||
void TaskContainer::stop()
|
void TaskContainer::stop()
|
||||||
{
|
{
|
||||||
if (!isRunning())
|
if (!isRunning())
|
||||||
@@ -597,6 +781,26 @@ void TaskNode::invokeEndHandler(bool success)
|
|||||||
m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
|
m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TaskNode::activateCondition()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_container.m_conditionData, return);
|
||||||
|
QTC_ASSERT(m_container.m_constData.m_taskTreePrivate->m_root->isRunning(), return);
|
||||||
|
|
||||||
|
if (m_container.m_conditionData->m_activated)
|
||||||
|
return; // Was already activated
|
||||||
|
|
||||||
|
m_container.m_conditionData->m_activated = true;
|
||||||
|
if (!isRunning())
|
||||||
|
return; // Condition not run yet or group already skipped or stopped
|
||||||
|
|
||||||
|
QTC_CHECK(m_container.m_runtimeData->m_doneCount == 0);
|
||||||
|
for (TaskNode *child : m_container.m_constData.m_children)
|
||||||
|
QTC_CHECK(!child->isRunning());
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, [this] { m_container.activateCondition(); },
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\class Utils::TaskTree
|
\class Utils::TaskTree
|
||||||
\inheaderfile utils/tasktree.h
|
\inheaderfile utils/tasktree.h
|
||||||
@@ -1324,6 +1528,9 @@ TaskTree::~TaskTree()
|
|||||||
{
|
{
|
||||||
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
|
||||||
"one of its handlers will lead to crash!"));
|
"one of its handlers will lead to crash!"));
|
||||||
|
if (isRunning())
|
||||||
|
d->deleteConditionActivators();
|
||||||
|
// TODO: delete storages explicitly here?
|
||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,6 +1539,7 @@ void TaskTree::setupRoot(const Tasking::Group &root)
|
|||||||
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
|
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
|
||||||
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the"
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the"
|
||||||
"TaskTree handlers, ingoring..."); return);
|
"TaskTree handlers, ingoring..."); return);
|
||||||
|
d->m_conditions.clear();
|
||||||
d->m_storages.clear();
|
d->m_storages.clear();
|
||||||
d->m_root.reset(new TaskNode(d, root, nullptr));
|
d->m_root.reset(new TaskNode(d, root, nullptr));
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,9 @@
|
|||||||
|
|
||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|
||||||
class StorageActivator;
|
class ExecutionContextActivator;
|
||||||
class TaskContainer;
|
class TaskContainer;
|
||||||
|
class TaskNode;
|
||||||
class TaskTreePrivate;
|
class TaskTreePrivate;
|
||||||
|
|
||||||
namespace Tasking {
|
namespace Tasking {
|
||||||
@@ -66,7 +67,7 @@ private:
|
|||||||
QSharedPointer<StorageData> m_storageData;
|
QSharedPointer<StorageData> m_storageData;
|
||||||
friend TaskContainer;
|
friend TaskContainer;
|
||||||
friend TaskTreePrivate;
|
friend TaskTreePrivate;
|
||||||
friend StorageActivator;
|
friend ExecutionContextActivator;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename StorageStruct>
|
template <typename StorageStruct>
|
||||||
@@ -87,6 +88,50 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT ConditionActivator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void activate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ConditionActivator(TaskNode *container) : m_node(container) {}
|
||||||
|
TaskNode *m_node = nullptr;
|
||||||
|
friend class Condition;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT Condition
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Condition();
|
||||||
|
ConditionActivator &operator*() const noexcept { return *activator(); }
|
||||||
|
ConditionActivator *operator->() const noexcept { return activator(); }
|
||||||
|
ConditionActivator *activator() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int createActivator(TaskNode *node) const;
|
||||||
|
void deleteActivator(int id) const;
|
||||||
|
void activateActivator(int id) const;
|
||||||
|
|
||||||
|
friend bool operator==(const Condition &first, const Condition &second)
|
||||||
|
{ return first.m_conditionData == second.m_conditionData; }
|
||||||
|
|
||||||
|
friend bool operator!=(const Condition &first, const Condition &second)
|
||||||
|
{ return first.m_conditionData != second.m_conditionData; }
|
||||||
|
|
||||||
|
friend size_t qHash(const Condition &storage, uint seed = 0)
|
||||||
|
{ return size_t(storage.m_conditionData.get()) ^ seed; }
|
||||||
|
|
||||||
|
struct ConditionData {
|
||||||
|
~ConditionData();
|
||||||
|
QHash<int, ConditionActivator *> m_activatorHash = {};
|
||||||
|
int m_activeActivator = 0; // 0 means no active activator
|
||||||
|
int m_activatorCounter = 0;
|
||||||
|
};
|
||||||
|
QSharedPointer<ConditionData> m_conditionData;
|
||||||
|
friend TaskTreePrivate;
|
||||||
|
friend ExecutionContextActivator;
|
||||||
|
};
|
||||||
|
|
||||||
// WorkflowPolicy:
|
// WorkflowPolicy:
|
||||||
// 1. When all children finished with done -> report done, otherwise:
|
// 1. When all children finished with done -> report done, otherwise:
|
||||||
// a) Report error on first error and stop executing other children (including their subtree)
|
// a) Report error on first error and stop executing other children (including their subtree)
|
||||||
@@ -143,11 +188,13 @@ public:
|
|||||||
TaskHandler taskHandler() const { return m_taskHandler; }
|
TaskHandler taskHandler() const { return m_taskHandler; }
|
||||||
GroupHandler groupHandler() const { return m_groupHandler; }
|
GroupHandler groupHandler() const { return m_groupHandler; }
|
||||||
QList<TaskItem> children() const { return m_children; }
|
QList<TaskItem> children() const { return m_children; }
|
||||||
|
std::optional<Condition> condition() const { return m_condition; }
|
||||||
QList<TreeStorageBase> storageList() const { return m_storageList; }
|
QList<TreeStorageBase> storageList() const { return m_storageList; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum class Type {
|
enum class Type {
|
||||||
Group,
|
Group,
|
||||||
|
Condition,
|
||||||
Storage,
|
Storage,
|
||||||
Limit,
|
Limit,
|
||||||
Policy,
|
Policy,
|
||||||
@@ -168,6 +215,9 @@ protected:
|
|||||||
TaskItem(const GroupHandler &handler)
|
TaskItem(const GroupHandler &handler)
|
||||||
: m_type(Type::GroupHandler)
|
: m_type(Type::GroupHandler)
|
||||||
, m_groupHandler(handler) {}
|
, m_groupHandler(handler) {}
|
||||||
|
TaskItem(const Condition &condition)
|
||||||
|
: m_type(Type::Condition)
|
||||||
|
, m_condition{condition} {}
|
||||||
TaskItem(const TreeStorageBase &storage)
|
TaskItem(const TreeStorageBase &storage)
|
||||||
: m_type(Type::Storage)
|
: m_type(Type::Storage)
|
||||||
, m_storageList{storage} {}
|
, m_storageList{storage} {}
|
||||||
@@ -179,6 +229,7 @@ private:
|
|||||||
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
||||||
TaskHandler m_taskHandler;
|
TaskHandler m_taskHandler;
|
||||||
GroupHandler m_groupHandler;
|
GroupHandler m_groupHandler;
|
||||||
|
std::optional<Condition> m_condition;
|
||||||
QList<TreeStorageBase> m_storageList;
|
QList<TreeStorageBase> m_storageList;
|
||||||
QList<TaskItem> m_children;
|
QList<TaskItem> m_children;
|
||||||
};
|
};
|
||||||
@@ -196,6 +247,12 @@ public:
|
|||||||
Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
|
Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT WaitFor : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaitFor(const Condition &condition) : TaskItem(condition) { }
|
||||||
|
};
|
||||||
|
|
||||||
class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem
|
class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -321,7 +378,7 @@ private:
|
|||||||
|
|
||||||
class TaskTreePrivate;
|
class TaskTreePrivate;
|
||||||
|
|
||||||
class QTCREATOR_UTILS_EXPORT TaskTree : public QObject
|
class QTCREATOR_UTILS_EXPORT TaskTree final : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@@ -20,7 +20,8 @@ enum class Handler {
|
|||||||
GroupSetup,
|
GroupSetup,
|
||||||
GroupDone,
|
GroupDone,
|
||||||
GroupError,
|
GroupError,
|
||||||
Sync
|
Sync,
|
||||||
|
Activator,
|
||||||
};
|
};
|
||||||
|
|
||||||
using Log = QList<QPair<int, Handler>>;
|
using Log = QList<QPair<int, Handler>>;
|
||||||
@@ -1053,25 +1054,65 @@ void tst_TaskTree::processTree_data()
|
|||||||
<< TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Failure};
|
<< TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Failure};
|
||||||
}
|
}
|
||||||
|
|
||||||
const Group root {
|
{
|
||||||
Storage(storage),
|
const Group root {
|
||||||
Sync(setupSync(1, true)),
|
Storage(storage),
|
||||||
Process(setupProcess(2)),
|
Sync(setupSync(1, true)),
|
||||||
Sync(setupSync(3, true)),
|
Process(setupProcess(2)),
|
||||||
Process(setupProcess(4)),
|
Sync(setupSync(3, true)),
|
||||||
Sync(setupSync(5, true)),
|
Process(setupProcess(4)),
|
||||||
OnGroupDone(groupDone(0))
|
Sync(setupSync(5, true)),
|
||||||
};
|
OnGroupDone(groupDone(0))
|
||||||
const Log log {
|
};
|
||||||
{1, Handler::Sync},
|
const Log log {
|
||||||
{2, Handler::Setup},
|
{1, Handler::Sync},
|
||||||
{3, Handler::Sync},
|
{2, Handler::Setup},
|
||||||
{4, Handler::Setup},
|
{3, Handler::Sync},
|
||||||
{5, Handler::Sync},
|
{4, Handler::Setup},
|
||||||
{0, Handler::GroupDone}
|
{5, Handler::Sync},
|
||||||
};
|
{0, Handler::GroupDone}
|
||||||
QTest::newRow("SyncAndAsync")
|
};
|
||||||
<< TestData{storage, root, log, 2, OnStart::Running, OnDone::Success};
|
QTest::newRow("SyncAndAsync")
|
||||||
|
<< TestData{storage, root, log, 2, OnStart::Running, OnDone::Success};
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Condition condition;
|
||||||
|
|
||||||
|
const auto setupProcessWithCondition
|
||||||
|
= [storage, condition, setupProcessHelper](int processId) {
|
||||||
|
return [storage, condition, setupProcessHelper, processId](QtcProcess &process) {
|
||||||
|
setupProcessHelper(process, {"-return", "0"}, processId);
|
||||||
|
CustomStorage *currentStorage = storage.activeStorage();
|
||||||
|
ConditionActivator *currentActivator = condition.activator();
|
||||||
|
connect(&process, &QtcProcess::started, [currentStorage, currentActivator, processId] {
|
||||||
|
currentStorage->m_log.append({processId, Handler::Activator});
|
||||||
|
currentActivator->activate();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group root {
|
||||||
|
parallel,
|
||||||
|
Storage(storage),
|
||||||
|
Process(setupProcessWithCondition(1)),
|
||||||
|
Group {
|
||||||
|
OnGroupSetup(groupSetup(2)),
|
||||||
|
WaitFor(condition),
|
||||||
|
Process(setupProcess(2)),
|
||||||
|
Process(setupProcess(3))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const Log log {
|
||||||
|
{1, Handler::Setup},
|
||||||
|
{2, Handler::GroupSetup},
|
||||||
|
{1, Handler::Activator},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{3, Handler::Setup}
|
||||||
|
};
|
||||||
|
QTest::newRow("WaitFor")
|
||||||
|
<< TestData{storage, root, log, 3, OnStart::Running, OnDone::Success};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_TaskTree::processTree()
|
void tst_TaskTree::processTree()
|
||||||
|
Reference in New Issue
Block a user