forked from qt-creator/qt-creator
Utils: Introduce TaskTree and Tasking namespace
The TaskTree class is responsible for running async task tree structure defined in a declarative way. Change-Id: Ieaf706c7d2efdc8b431a17b2db8b28bf4b7c38e5 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -158,6 +158,7 @@ add_qtc_library(Utils
|
|||||||
stringutils.cpp stringutils.h
|
stringutils.cpp stringutils.h
|
||||||
styledbar.cpp styledbar.h
|
styledbar.cpp styledbar.h
|
||||||
stylehelper.cpp stylehelper.h
|
stylehelper.cpp stylehelper.h
|
||||||
|
tasktree.cpp tasktree.h
|
||||||
templateengine.cpp templateengine.h
|
templateengine.cpp templateengine.h
|
||||||
temporarydirectory.cpp temporarydirectory.h
|
temporarydirectory.cpp temporarydirectory.h
|
||||||
temporaryfile.cpp temporaryfile.h
|
temporaryfile.cpp temporaryfile.h
|
||||||
|
|||||||
@@ -2091,6 +2091,18 @@ void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value)
|
|||||||
setProperty(QTC_PROCESS_BLOCKING_TYPE, value);
|
setProperty(QTC_PROCESS_BLOCKING_TYPE, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QtcProcessAdapter::QtcProcessAdapter()
|
||||||
|
{
|
||||||
|
connect(task(), &QtcProcess::done, this, [this] {
|
||||||
|
emit done(task()->result() == ProcessResult::FinishedWithSuccess);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtcProcessAdapter::start()
|
||||||
|
{
|
||||||
|
task()->start();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|
||||||
#include "qtcprocess.moc"
|
#include "qtcprocess.moc"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "commandline.h"
|
#include "commandline.h"
|
||||||
#include "processenums.h"
|
#include "processenums.h"
|
||||||
|
#include "tasktree.h"
|
||||||
|
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
|
||||||
@@ -202,4 +203,13 @@ public:
|
|||||||
std::function<Environment(const FilePath &)> systemEnvironmentForBinary;
|
std::function<Environment(const FilePath &)> systemEnvironmentForBinary;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT QtcProcessAdapter : public Tasking::TaskAdapter<QtcProcess>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QtcProcessAdapter();
|
||||||
|
void start() final;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|
||||||
|
QTC_DECLARE_CUSTOM_TASK(Process, Utils::QtcProcessAdapter);
|
||||||
|
|||||||
382
src/libs/utils/tasktree.cpp
Normal file
382
src/libs/utils/tasktree.cpp
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "tasktree.h"
|
||||||
|
|
||||||
|
#include "guard.h"
|
||||||
|
#include "qtcassert.h"
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
namespace Tasking {
|
||||||
|
|
||||||
|
ExecuteInSequence sequential;
|
||||||
|
ExecuteInParallel parallel;
|
||||||
|
WorkflowPolicy stopOnError(TaskItem::WorkflowPolicy::StopOnError);
|
||||||
|
WorkflowPolicy continueOnError(TaskItem::WorkflowPolicy::ContinueOnError);
|
||||||
|
WorkflowPolicy stopOnDone(TaskItem::WorkflowPolicy::StopOnDone);
|
||||||
|
WorkflowPolicy continueOnDone(TaskItem::WorkflowPolicy::ContinueOnDone);
|
||||||
|
WorkflowPolicy optional(TaskItem::WorkflowPolicy::Optional);
|
||||||
|
|
||||||
|
void TaskItem::addChildren(const QList<TaskItem> &children)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return);
|
||||||
|
for (const TaskItem &child : children) {
|
||||||
|
switch (child.m_type) {
|
||||||
|
case Type::Group:
|
||||||
|
m_children.append(child);
|
||||||
|
break;
|
||||||
|
case Type::Mode:
|
||||||
|
QTC_ASSERT(m_type == Type::Group,
|
||||||
|
qWarning("Mode may only be a child of Group, skipping..."); return);
|
||||||
|
m_executeMode = child.m_executeMode; // TODO: Assert on redefinition?
|
||||||
|
break;
|
||||||
|
case Type::Policy:
|
||||||
|
QTC_ASSERT(m_type == Type::Group,
|
||||||
|
qWarning("Workflow Policy may only be a child of Group, skipping..."); return);
|
||||||
|
m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition?
|
||||||
|
break;
|
||||||
|
case Type::TaskHandler:
|
||||||
|
QTC_ASSERT(child.m_taskHandler.m_createHandler,
|
||||||
|
qWarning("Task Create Handler can't be null, skipping..."); return);
|
||||||
|
QTC_ASSERT(child.m_taskHandler.m_setupHandler,
|
||||||
|
qWarning("Task Setup Handler can't be null, skipping..."); return);
|
||||||
|
m_children.append(child);
|
||||||
|
break;
|
||||||
|
case Type::GroupHandler:
|
||||||
|
QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
|
||||||
|
"child of Group, skipping..."); break);
|
||||||
|
QTC_ASSERT(!child.m_groupHandler.m_simpleSetupHandler
|
||||||
|
|| !m_groupHandler.m_simpleSetupHandler,
|
||||||
|
qWarning("Group Setup Handler redefinition, overriding..."));
|
||||||
|
QTC_ASSERT(!child.m_groupHandler.m_simpleDoneHandler
|
||||||
|
|| !m_groupHandler.m_simpleDoneHandler,
|
||||||
|
qWarning("Group Done Handler redefinition, overriding..."));
|
||||||
|
QTC_ASSERT(!child.m_groupHandler.m_simpleErrorHandler
|
||||||
|
|| !m_groupHandler.m_simpleErrorHandler,
|
||||||
|
qWarning("Group Error Handler redefinition, overriding..."));
|
||||||
|
if (child.m_groupHandler.m_simpleSetupHandler)
|
||||||
|
m_groupHandler.m_simpleSetupHandler = child.m_groupHandler.m_simpleSetupHandler;
|
||||||
|
if (child.m_groupHandler.m_simpleDoneHandler)
|
||||||
|
m_groupHandler.m_simpleDoneHandler = child.m_groupHandler.m_simpleDoneHandler;
|
||||||
|
if (child.m_groupHandler.m_simpleErrorHandler)
|
||||||
|
m_groupHandler.m_simpleErrorHandler = child.m_groupHandler.m_simpleErrorHandler;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Tasking
|
||||||
|
|
||||||
|
using namespace Tasking;
|
||||||
|
|
||||||
|
class TaskTreePrivate;
|
||||||
|
class TaskNode;
|
||||||
|
|
||||||
|
class TaskContainer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer,
|
||||||
|
const TaskItem &task);
|
||||||
|
~TaskContainer();
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
bool isRunning() const;
|
||||||
|
void childDone(bool success);
|
||||||
|
void invokeSubTreeHandler(bool success);
|
||||||
|
void resetSuccessBit();
|
||||||
|
void updateSuccessBit(bool success);
|
||||||
|
|
||||||
|
TaskTreePrivate *m_taskTreePrivate = nullptr;
|
||||||
|
TaskContainer *m_parentContainer = nullptr;
|
||||||
|
const TaskItem::ExecuteMode m_executeMode = TaskItem::ExecuteMode::Parallel;
|
||||||
|
TaskItem::WorkflowPolicy m_workflowPolicy = TaskItem::WorkflowPolicy::StopOnError;
|
||||||
|
const TaskItem::GroupHandler m_groupHandler;
|
||||||
|
QList<TaskNode *> m_children;
|
||||||
|
int m_currentIndex = -1;
|
||||||
|
bool m_successBit = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskNode : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TaskNode(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer,
|
||||||
|
const TaskItem &task)
|
||||||
|
: m_taskHandler(task.taskHandler())
|
||||||
|
, m_container(taskTreePrivate, parentContainer, task)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start();
|
||||||
|
void stop();
|
||||||
|
bool isRunning();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const TaskItem::TaskHandler m_taskHandler;
|
||||||
|
TaskContainer m_container;
|
||||||
|
std::unique_ptr<TaskInterface> m_task;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskTreePrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TaskTreePrivate(TaskTree *taskTree, const Group &root)
|
||||||
|
: q(taskTree)
|
||||||
|
, m_root(this, nullptr, root) {}
|
||||||
|
|
||||||
|
void emitDone() {
|
||||||
|
GuardLocker locker(m_guard);
|
||||||
|
emit q->done();
|
||||||
|
}
|
||||||
|
void emitError() {
|
||||||
|
GuardLocker locker(m_guard);
|
||||||
|
emit q->errorOccurred();
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskTree *q = nullptr;
|
||||||
|
TaskNode m_root;
|
||||||
|
Guard m_guard;
|
||||||
|
};
|
||||||
|
|
||||||
|
TaskContainer::TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer,
|
||||||
|
const TaskItem &task)
|
||||||
|
: m_taskTreePrivate(taskTreePrivate)
|
||||||
|
, m_parentContainer(parentContainer)
|
||||||
|
, m_executeMode(task.executeMode())
|
||||||
|
, m_workflowPolicy(task.workflowPolicy())
|
||||||
|
, m_groupHandler(task.groupHandler())
|
||||||
|
{
|
||||||
|
const QList<TaskItem> &children = task.children();
|
||||||
|
for (const TaskItem &child : children)
|
||||||
|
m_children.append(new TaskNode(m_taskTreePrivate, this, child));
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskContainer::~TaskContainer()
|
||||||
|
{
|
||||||
|
qDeleteAll(m_children);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::start()
|
||||||
|
{
|
||||||
|
if (m_groupHandler.m_simpleSetupHandler) {
|
||||||
|
GuardLocker locker(m_taskTreePrivate->m_guard);
|
||||||
|
m_groupHandler.m_simpleSetupHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_children.isEmpty()) {
|
||||||
|
invokeSubTreeHandler(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentIndex = 0;
|
||||||
|
resetSuccessBit();
|
||||||
|
|
||||||
|
if (m_executeMode == TaskItem::ExecuteMode::Sequential) {
|
||||||
|
m_children.at(m_currentIndex)->start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel case
|
||||||
|
for (TaskNode *child : std::as_const(m_children)) {
|
||||||
|
if (!child->start())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::stop()
|
||||||
|
{
|
||||||
|
m_currentIndex = -1;
|
||||||
|
for (TaskNode *child : std::as_const(m_children))
|
||||||
|
child->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskContainer::isRunning() const
|
||||||
|
{
|
||||||
|
return m_currentIndex >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::childDone(bool success)
|
||||||
|
{
|
||||||
|
if ((m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone && success)
|
||||||
|
|| (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnError && !success)) {
|
||||||
|
stop();
|
||||||
|
invokeSubTreeHandler(success);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++m_currentIndex;
|
||||||
|
updateSuccessBit(success);
|
||||||
|
|
||||||
|
if (m_currentIndex == m_children.size()) {
|
||||||
|
invokeSubTreeHandler(m_successBit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_executeMode == TaskItem::ExecuteMode::Sequential)
|
||||||
|
m_children.at(m_currentIndex)->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::invokeSubTreeHandler(bool success)
|
||||||
|
{
|
||||||
|
m_currentIndex = -1;
|
||||||
|
m_successBit = success;
|
||||||
|
if (success && m_groupHandler.m_simpleDoneHandler) {
|
||||||
|
GuardLocker locker(m_taskTreePrivate->m_guard);
|
||||||
|
m_groupHandler.m_simpleDoneHandler();
|
||||||
|
} else if (!success && m_groupHandler.m_simpleErrorHandler) {
|
||||||
|
GuardLocker locker(m_taskTreePrivate->m_guard);
|
||||||
|
m_groupHandler.m_simpleErrorHandler();
|
||||||
|
}
|
||||||
|
if (m_parentContainer) {
|
||||||
|
m_parentContainer->childDone(success);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (success)
|
||||||
|
m_taskTreePrivate->emitDone();
|
||||||
|
else
|
||||||
|
m_taskTreePrivate->emitError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::resetSuccessBit()
|
||||||
|
{
|
||||||
|
if (m_children.isEmpty())
|
||||||
|
m_successBit = true;
|
||||||
|
|
||||||
|
if (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone
|
||||||
|
|| m_workflowPolicy == TaskItem::WorkflowPolicy::ContinueOnDone) {
|
||||||
|
m_successBit = false;
|
||||||
|
} else {
|
||||||
|
m_successBit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::updateSuccessBit(bool success)
|
||||||
|
{
|
||||||
|
if (m_workflowPolicy == TaskItem::WorkflowPolicy::Optional)
|
||||||
|
return;
|
||||||
|
if (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone
|
||||||
|
|| m_workflowPolicy == TaskItem::WorkflowPolicy::ContinueOnDone) {
|
||||||
|
m_successBit = m_successBit || success;
|
||||||
|
} else {
|
||||||
|
m_successBit = m_successBit && success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool TaskNode::start()
|
||||||
|
{
|
||||||
|
if (!m_taskHandler.m_createHandler || !m_taskHandler.m_setupHandler) {
|
||||||
|
m_container.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
m_task.reset(m_taskHandler.m_createHandler());
|
||||||
|
{
|
||||||
|
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
||||||
|
m_taskHandler.m_setupHandler(*m_task.get());
|
||||||
|
}
|
||||||
|
connect(m_task.get(), &TaskInterface::done, this, [this](bool success) {
|
||||||
|
if (success && m_taskHandler.m_doneHandler) {
|
||||||
|
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
||||||
|
m_taskHandler.m_doneHandler(*m_task.get());
|
||||||
|
} else if (!success && m_taskHandler.m_errorHandler) {
|
||||||
|
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
||||||
|
m_taskHandler.m_errorHandler(*m_task.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_task.release()->deleteLater();
|
||||||
|
|
||||||
|
QTC_CHECK(m_container.m_parentContainer);
|
||||||
|
m_container.m_parentContainer->childDone(success);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_task->start();
|
||||||
|
return m_task.get(); // In case of failed to start, done handler already released process
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskNode::stop()
|
||||||
|
{
|
||||||
|
m_task.reset();
|
||||||
|
m_container.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskNode::isRunning()
|
||||||
|
{
|
||||||
|
return m_task || m_container.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class Utils::TaskTree
|
||||||
|
|
||||||
|
\brief The TaskTree class is responsible for running async task tree structure defined in a
|
||||||
|
declarative way.
|
||||||
|
|
||||||
|
The Tasking namespace (similar to Layouting) is designer for building declarative task
|
||||||
|
tree structure. The examples of tasks that can be used inside TaskTree are e.g. QtcProcess,
|
||||||
|
FileTransfer, AsyncTask<>. It's extensible, so any possible asynchronous task may be
|
||||||
|
integrated and used inside TaskTree. TaskTree enables to form sophisticated mixtures of
|
||||||
|
parallel or sequential flow of tasks in tree form.
|
||||||
|
|
||||||
|
The TaskTree consist of Group root element. The Group can have nested Group elements.
|
||||||
|
The Group may also contain any number of tasks, e.g. Process, FileTransfer,
|
||||||
|
AsyncTask<ReturnType>.
|
||||||
|
|
||||||
|
Each Group can contain various other elements describing the processing flow.
|
||||||
|
|
||||||
|
The execute mode elements of a Group specify how direct children of a Group will be executed.
|
||||||
|
The "sequential" element of a Group means all tasks in a group will be executed in chain,
|
||||||
|
so after the previous task finished, the next will be started. This is the default Group
|
||||||
|
behavior. The "parallel" element of a Group means that all tasks in a Group will be started
|
||||||
|
simultaneously. When having nested Groups hierarchy, we may mix execute modes freely
|
||||||
|
and each Group will be executed according to its own execute mode.
|
||||||
|
The "sequential" mode may be very useful in cases when result data from one task is need to
|
||||||
|
be passed as an input data to the other task - sequential mode guarantees that the next
|
||||||
|
task will be started only after the previous task has already finished.
|
||||||
|
|
||||||
|
There are many possible "workflow" behaviors for the Group. E.g. "stopOnError",
|
||||||
|
the default Group workflow behavior, means that whenever any direct child of a Group
|
||||||
|
finished with error, we immediately stop processing other tasks in this group
|
||||||
|
(in parallel case) by canceling them and immediately finish the Group with error.
|
||||||
|
|
||||||
|
The user of TaskTree specifies how to setup his tasks (by providing TaskSetupHandlers)
|
||||||
|
and how to collect output data from the finished tasks (by providing TaskEndHandlers).
|
||||||
|
The user don't need to create tasks manually - TaskTree will create them when it's needed
|
||||||
|
and destroy when they are not used anymore.
|
||||||
|
|
||||||
|
Whenever a Group elemenent is being started, the Group's OnGroupSetup handler is being called.
|
||||||
|
Just after the handler finishes, all Group's children are executed (either in parallel or
|
||||||
|
in sequence). When all Group's children finished, one of Group's OnGroupDone or OnGroupError
|
||||||
|
is being executed, depending on results of children execution and Group's workflow policy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
TaskTree::TaskTree(const Group &root)
|
||||||
|
: d(new TaskTreePrivate(this, root))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskTree::~TaskTree()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
|
||||||
|
"one of its handlers will lead to crash!"));
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTree::start()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
|
||||||
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the"
|
||||||
|
"TaskTree handlers, ingoring..."); return);
|
||||||
|
d->m_root.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskTree::stop()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the"
|
||||||
|
"TaskTree handlers, ingoring..."); return);
|
||||||
|
d->m_root.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskTree::isRunning() const
|
||||||
|
{
|
||||||
|
return d->m_root.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Utils
|
||||||
239
src/libs/utils/tasktree.h
Normal file
239
src/libs/utils/tasktree.h
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils_global.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
namespace Tasking {
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT TaskInterface : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
TaskInterface() = default;
|
||||||
|
virtual void start() = 0;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void done(bool success);
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Internal, provided by QTC_DECLARE_CUSTOM_TASK
|
||||||
|
using TaskCreateHandler = std::function<TaskInterface *(void)>;
|
||||||
|
// Called prior to task start, just after createHandler
|
||||||
|
using TaskSetupHandler = std::function<void(TaskInterface &)>;
|
||||||
|
// Called on task done / error
|
||||||
|
using TaskEndHandler = std::function<void(const TaskInterface &)>;
|
||||||
|
// Called when sub tree entered / after sub three ended with success or failure
|
||||||
|
using GroupSimpleHandler = std::function<void()>;
|
||||||
|
|
||||||
|
struct TaskHandler {
|
||||||
|
TaskCreateHandler m_createHandler;
|
||||||
|
TaskSetupHandler m_setupHandler;
|
||||||
|
TaskEndHandler m_doneHandler;
|
||||||
|
TaskEndHandler m_errorHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GroupHandler {
|
||||||
|
GroupSimpleHandler m_simpleSetupHandler;
|
||||||
|
GroupSimpleHandler m_simpleDoneHandler;
|
||||||
|
GroupSimpleHandler m_simpleErrorHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ExecuteMode {
|
||||||
|
Parallel, // default
|
||||||
|
Sequential
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4 policies:
|
||||||
|
// 1. When all children finished with done -> report done, otherwise:
|
||||||
|
// a) Report error on first error and stop executing other children (including their subtree)
|
||||||
|
// b) On first error - wait for all children to be finished and report error afterwards
|
||||||
|
// 2. When all children finished with error -> report error, otherwise:
|
||||||
|
// a) Report done on first done and stop executing other children (including their subtree)
|
||||||
|
// b) On first done - wait for all children to be finished and report done afterwards
|
||||||
|
|
||||||
|
enum class WorkflowPolicy {
|
||||||
|
StopOnError, // 1a - Will report error on any child error, otherwise done (if all children were done)
|
||||||
|
ContinueOnError, // 1b - the same. When no children it reports done.
|
||||||
|
StopOnDone, // 2a - Will report done on any child done, otherwise error (if all children were error)
|
||||||
|
ContinueOnDone, // 2b - the same. When no children it reports done. (?)
|
||||||
|
Optional // Returns always done after all children finished
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecuteMode executeMode() const { return m_executeMode; }
|
||||||
|
WorkflowPolicy workflowPolicy() const { return m_workflowPolicy; }
|
||||||
|
TaskHandler taskHandler() const { return m_taskHandler; }
|
||||||
|
GroupHandler groupHandler() const { return m_groupHandler; }
|
||||||
|
QList<TaskItem> children() const { return m_children; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum class Type {
|
||||||
|
Group,
|
||||||
|
Mode,
|
||||||
|
Policy,
|
||||||
|
TaskHandler,
|
||||||
|
GroupHandler
|
||||||
|
// TODO: Add Cond type (with CondHandler and True and False branches)?
|
||||||
|
};
|
||||||
|
|
||||||
|
TaskItem() = default;
|
||||||
|
TaskItem(ExecuteMode mode)
|
||||||
|
: m_type(Type::Mode)
|
||||||
|
, m_executeMode(mode) {}
|
||||||
|
TaskItem(WorkflowPolicy policy)
|
||||||
|
: m_type(Type::Policy)
|
||||||
|
, m_workflowPolicy(policy) {}
|
||||||
|
TaskItem(const TaskHandler &handler)
|
||||||
|
: m_type(Type::TaskHandler)
|
||||||
|
, m_taskHandler(handler) {}
|
||||||
|
TaskItem(const GroupHandler &handler)
|
||||||
|
: m_type(Type::GroupHandler)
|
||||||
|
, m_groupHandler(handler) {}
|
||||||
|
void addChildren(const QList<TaskItem> &children);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Type m_type = Type::Group;
|
||||||
|
ExecuteMode m_executeMode = ExecuteMode::Sequential;
|
||||||
|
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
||||||
|
TaskHandler m_taskHandler;
|
||||||
|
GroupHandler m_groupHandler;
|
||||||
|
QList<TaskItem> m_children;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT Group : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Group(const QList<TaskItem> &children) { addChildren(children); }
|
||||||
|
Group(std::initializer_list<TaskItem> children) { addChildren(children); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT ExecuteInSequence : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExecuteInSequence() : TaskItem(ExecuteMode::Sequential) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT ExecuteInParallel : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExecuteInParallel() : TaskItem(ExecuteMode::Parallel) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT WorkflowPolicy : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WorkflowPolicy(TaskItem::WorkflowPolicy policy) : TaskItem(policy) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT OnGroupSetup : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OnGroupSetup(const GroupSimpleHandler &handler) : TaskItem({{handler}, {}, {}}) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT OnGroupDone : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OnGroupDone(const GroupSimpleHandler &handler) : TaskItem({{}, handler, {}}) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT OnGroupError : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OnGroupError(const GroupSimpleHandler &handler) : TaskItem({{}, {}, handler}) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTCREATOR_UTILS_EXPORT extern ExecuteInSequence sequential;
|
||||||
|
QTCREATOR_UTILS_EXPORT extern ExecuteInParallel parallel;
|
||||||
|
QTCREATOR_UTILS_EXPORT extern WorkflowPolicy stopOnError;
|
||||||
|
QTCREATOR_UTILS_EXPORT extern WorkflowPolicy continueOnError;
|
||||||
|
QTCREATOR_UTILS_EXPORT extern WorkflowPolicy stopOnDone;
|
||||||
|
QTCREATOR_UTILS_EXPORT extern WorkflowPolicy continueOnDone;
|
||||||
|
QTCREATOR_UTILS_EXPORT extern WorkflowPolicy optional;
|
||||||
|
|
||||||
|
template <typename Task>
|
||||||
|
class TaskAdapter : public TaskInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Type = Task;
|
||||||
|
TaskAdapter() = default;
|
||||||
|
Task *task() { return &m_task; }
|
||||||
|
const Task *task() const { return &m_task; }
|
||||||
|
private:
|
||||||
|
Task m_task;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Adapter>
|
||||||
|
class CustomTask : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Task = typename Adapter::Type;
|
||||||
|
using SetupHandler = std::function<void(Task &)>;
|
||||||
|
using EndHandler = std::function<void(const Task &)>;
|
||||||
|
static Adapter *createAdapter() { return new Adapter; }
|
||||||
|
CustomTask(const SetupHandler &setup, const EndHandler &done = {}, const EndHandler &error = {})
|
||||||
|
: TaskItem({&createAdapter, wrapSetup(setup),
|
||||||
|
wrapEnd(done), wrapEnd(error)}) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static TaskSetupHandler wrapSetup(SetupHandler handler) {
|
||||||
|
if (!handler)
|
||||||
|
return {};
|
||||||
|
return [handler](TaskInterface &taskInterface) {
|
||||||
|
Adapter &adapter = static_cast<Adapter &>(taskInterface);
|
||||||
|
handler(*adapter.task());
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static TaskEndHandler wrapEnd(EndHandler handler) {
|
||||||
|
if (!handler)
|
||||||
|
return {};
|
||||||
|
return [handler](const TaskInterface &taskInterface) {
|
||||||
|
const Adapter &adapter = static_cast<const Adapter &>(taskInterface);
|
||||||
|
handler(*adapter.task());
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Tasking
|
||||||
|
|
||||||
|
class TaskTreePrivate;
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT TaskTree : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
TaskTree(const Tasking::Group &root);
|
||||||
|
~TaskTree();
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
bool isRunning() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void done();
|
||||||
|
void errorOccurred();
|
||||||
|
|
||||||
|
private:
|
||||||
|
TaskTreePrivate *d;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Utils
|
||||||
|
|
||||||
|
#define QTC_DECLARE_CUSTOM_TASK(CustomTaskName, TaskAdapterClass)\
|
||||||
|
namespace Utils::Tasking { using CustomTaskName = CustomTask<TaskAdapterClass>; }
|
||||||
|
|
||||||
|
#define QTC_DECLARE_CUSTOM_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\
|
||||||
|
namespace Utils::Tasking {\
|
||||||
|
template <typename ...Args>\
|
||||||
|
using CustomTaskName = CustomTask<TaskAdapterClass<Args...>>;\
|
||||||
|
} // namespace Utils::Tasking
|
||||||
|
|
||||||
@@ -292,6 +292,8 @@ Project {
|
|||||||
"styledbar.h",
|
"styledbar.h",
|
||||||
"stylehelper.cpp",
|
"stylehelper.cpp",
|
||||||
"stylehelper.h",
|
"stylehelper.h",
|
||||||
|
"tasktree.cpp",
|
||||||
|
"tasktree.h",
|
||||||
"templateengine.cpp",
|
"templateengine.cpp",
|
||||||
"templateengine.h",
|
"templateengine.h",
|
||||||
"temporarydirectory.cpp",
|
"temporarydirectory.cpp",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ add_subdirectory(qtcprocess)
|
|||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
add_subdirectory(stringutils)
|
add_subdirectory(stringutils)
|
||||||
add_subdirectory(templateengine)
|
add_subdirectory(templateengine)
|
||||||
|
add_subdirectory(tasktree)
|
||||||
add_subdirectory(treemodel)
|
add_subdirectory(treemodel)
|
||||||
add_subdirectory(multicursor)
|
add_subdirectory(multicursor)
|
||||||
add_subdirectory(deviceshell)
|
add_subdirectory(deviceshell)
|
||||||
|
|||||||
11
tests/auto/utils/tasktree/CMakeLists.txt
Normal file
11
tests/auto/utils/tasktree/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
add_subdirectory(testapp)
|
||||||
|
|
||||||
|
file(RELATIVE_PATH RELATIVE_TEST_PATH "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
|
file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_LIBEXEC_PATH}")
|
||||||
|
|
||||||
|
add_qtc_test(tst_utils_tasktree
|
||||||
|
DEFINES "TEST_RELATIVE_LIBEXEC_PATH=\"${TEST_RELATIVE_LIBEXEC_PATH}\""
|
||||||
|
"TESTAPP_PATH=\"${CMAKE_CURRENT_BINARY_DIR}/testapp\""
|
||||||
|
DEPENDS Utils app_version
|
||||||
|
SOURCES tst_tasktree.cpp
|
||||||
|
)
|
||||||
27
tests/auto/utils/tasktree/tasktree.qbs
Normal file
27
tests/auto/utils/tasktree/tasktree.qbs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import qbs.FileInfo
|
||||||
|
|
||||||
|
Project {
|
||||||
|
QtcAutotest {
|
||||||
|
name: "TaskTree autotest"
|
||||||
|
|
||||||
|
Depends { name: "Utils" }
|
||||||
|
Depends { name: "app_version_header" }
|
||||||
|
|
||||||
|
files: [
|
||||||
|
"tst_tasktree.cpp",
|
||||||
|
]
|
||||||
|
cpp.defines: {
|
||||||
|
var defines = base;
|
||||||
|
if (qbs.targetOS === "windows")
|
||||||
|
defines.push("_CRT_SECURE_NO_WARNINGS");
|
||||||
|
var absLibExecPath = FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix,
|
||||||
|
qtc.ide_libexec_path);
|
||||||
|
var relLibExecPath = FileInfo.relativePath(destinationDirectory, absLibExecPath);
|
||||||
|
defines.push('TEST_RELATIVE_LIBEXEC_PATH="' + relLibExecPath + '"');
|
||||||
|
defines.push('TESTAPP_PATH="'
|
||||||
|
+ FileInfo.joinPaths(destinationDirectory, "testapp") + '"');
|
||||||
|
return defines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
references: "testapp/testapp.qbs"
|
||||||
|
}
|
||||||
12
tests/auto/utils/tasktree/testapp/CMakeLists.txt
Normal file
12
tests/auto/utils/tasktree/testapp/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
add_qtc_executable(testapp
|
||||||
|
DEPENDS Utils
|
||||||
|
SOURCES main.cpp
|
||||||
|
SKIP_INSTALL
|
||||||
|
INTERNAL_ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(testapp PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
)
|
||||||
45
tests/auto/utils/tasktree/testapp/main.cpp
Normal file
45
tests/auto/utils/tasktree/testapp/main.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include <app/app_version.h>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <crtdbg.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char CRASH_OPTION[] = "-crash";
|
||||||
|
const char RETURN_OPTION[] = "-return";
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// avoid crash reporter dialog
|
||||||
|
_set_error_mode(_OUT_TO_STDERR);
|
||||||
|
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (argc > 1) {
|
||||||
|
const auto arg = QString::fromLocal8Bit(argv[1]);
|
||||||
|
if (arg == CRASH_OPTION) {
|
||||||
|
qFatal("The application has crashed purposefully!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (arg == RETURN_OPTION) {
|
||||||
|
if (argc > 2) {
|
||||||
|
const auto retString = QString::fromLocal8Bit(argv[2]);
|
||||||
|
bool ok = false;
|
||||||
|
const int retVal = retString.toInt(&ok);
|
||||||
|
if (ok)
|
||||||
|
return retVal;
|
||||||
|
// not an int return value
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// lacking return value
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// not recognized option
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
19
tests/auto/utils/tasktree/testapp/testapp.qbs
Normal file
19
tests/auto/utils/tasktree/testapp/testapp.qbs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import qbs.FileInfo
|
||||||
|
|
||||||
|
QtApplication {
|
||||||
|
name: "testapp"
|
||||||
|
Depends { name: "qtc" }
|
||||||
|
Depends { name: "Utils" }
|
||||||
|
|
||||||
|
consoleApplication: true
|
||||||
|
cpp.cxxLanguageVersion: "c++17"
|
||||||
|
cpp.rpaths: project.buildDirectory + '/' + qtc.ide_library_path
|
||||||
|
|
||||||
|
install: false
|
||||||
|
destinationDirectory: project.buildDirectory + '/'
|
||||||
|
+ FileInfo.relativePath(project.ide_source_tree, sourceDirectory)
|
||||||
|
|
||||||
|
files: [
|
||||||
|
"main.cpp",
|
||||||
|
]
|
||||||
|
}
|
||||||
542
tests/auto/utils/tasktree/tst_tasktree.cpp
Normal file
542
tests/auto/utils/tasktree/tst_tasktree.cpp
Normal file
@@ -0,0 +1,542 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include <app/app_version.h>
|
||||||
|
|
||||||
|
#include <utils/launcherinterface.h>
|
||||||
|
#include <utils/qtcprocess.h>
|
||||||
|
#include <utils/singleton.h>
|
||||||
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
enum class Handler {
|
||||||
|
Setup,
|
||||||
|
Done,
|
||||||
|
Error,
|
||||||
|
GroupSetup,
|
||||||
|
GroupDone,
|
||||||
|
GroupError
|
||||||
|
};
|
||||||
|
|
||||||
|
using Log = QList<QPair<int, Handler>>;
|
||||||
|
|
||||||
|
class tst_TaskTree : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase();
|
||||||
|
|
||||||
|
void validConstructs(); // compile test
|
||||||
|
void processTree_data();
|
||||||
|
void processTree();
|
||||||
|
void storage_data();
|
||||||
|
void storage();
|
||||||
|
|
||||||
|
void cleanupTestCase();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Log m_log;
|
||||||
|
FilePath m_testAppPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
void tst_TaskTree::initTestCase()
|
||||||
|
{
|
||||||
|
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
|
||||||
|
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
|
const QString libExecPath(qApp->applicationDirPath() + '/'
|
||||||
|
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
||||||
|
LauncherInterface::setPathToLauncher(libExecPath);
|
||||||
|
m_testAppPath = FilePath::fromString(QLatin1String(TESTAPP_PATH)
|
||||||
|
+ QLatin1String("/testapp")).withExecutableSuffix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_TaskTree::cleanupTestCase()
|
||||||
|
{
|
||||||
|
Utils::Singleton::deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_TaskTree::validConstructs()
|
||||||
|
{
|
||||||
|
using namespace Tasking;
|
||||||
|
|
||||||
|
const Group process {
|
||||||
|
parallel,
|
||||||
|
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
|
||||||
|
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
|
||||||
|
Process([](QtcProcess &) {}, [](const QtcProcess &) {})
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group group1 {
|
||||||
|
process
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group group2 {
|
||||||
|
parallel,
|
||||||
|
Group {
|
||||||
|
parallel,
|
||||||
|
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
|
||||||
|
Group {
|
||||||
|
parallel,
|
||||||
|
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
|
||||||
|
Group {
|
||||||
|
parallel,
|
||||||
|
Process([](QtcProcess &) {}, [](const QtcProcess &) {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Group {
|
||||||
|
parallel,
|
||||||
|
Process([](QtcProcess &) {}, [](const QtcProcess &) {}),
|
||||||
|
OnGroupDone([] {}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
process,
|
||||||
|
OnGroupDone([] {}),
|
||||||
|
OnGroupError([] {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char s_processIdProperty[] = "__processId";
|
||||||
|
|
||||||
|
void tst_TaskTree::processTree_data()
|
||||||
|
{
|
||||||
|
using namespace Tasking;
|
||||||
|
using namespace std::placeholders;
|
||||||
|
|
||||||
|
QTest::addColumn<Group>("root");
|
||||||
|
QTest::addColumn<Log>("expectedLog");
|
||||||
|
QTest::addColumn<bool>("runningAfterStart");
|
||||||
|
QTest::addColumn<bool>("success");
|
||||||
|
|
||||||
|
const auto setupProcessHelper = [this](QtcProcess &process, const QStringList &args, int processId) {
|
||||||
|
process.setCommand(CommandLine(m_testAppPath, args));
|
||||||
|
process.setProperty(s_processIdProperty, processId);
|
||||||
|
m_log.append({processId, Handler::Setup});
|
||||||
|
};
|
||||||
|
const auto setupProcess = [setupProcessHelper](QtcProcess &process, int processId) {
|
||||||
|
setupProcessHelper(process, {"-return", "0"}, processId);
|
||||||
|
};
|
||||||
|
// const auto setupErrorProcess = [setupProcessHelper](QtcProcess &process, int processId) {
|
||||||
|
// setupProcessHelper(process, {"-return", "1"}, processId);
|
||||||
|
// };
|
||||||
|
const auto setupCrashProcess = [setupProcessHelper](QtcProcess &process, int processId) {
|
||||||
|
setupProcessHelper(process, {"-crash"}, processId);
|
||||||
|
};
|
||||||
|
const auto readResultAnonymous = [this](const QtcProcess &) {
|
||||||
|
m_log.append({-1, Handler::Done});
|
||||||
|
};
|
||||||
|
const auto readResult = [this](const QtcProcess &process) {
|
||||||
|
const int processId = process.property(s_processIdProperty).toInt();
|
||||||
|
m_log.append({processId, Handler::Done});
|
||||||
|
};
|
||||||
|
const auto readError = [this](const QtcProcess &process) {
|
||||||
|
const int processId = process.property(s_processIdProperty).toInt();
|
||||||
|
m_log.append({processId, Handler::Error});
|
||||||
|
};
|
||||||
|
const auto groupSetup = [this](int processId) {
|
||||||
|
m_log.append({processId, Handler::GroupSetup});
|
||||||
|
};
|
||||||
|
const auto groupDone = [this](int processId) {
|
||||||
|
m_log.append({processId, Handler::GroupDone});
|
||||||
|
};
|
||||||
|
const auto rootDone = [this] {
|
||||||
|
m_log.append({-1, Handler::GroupDone});
|
||||||
|
};
|
||||||
|
const auto rootError = [this] {
|
||||||
|
m_log.append({-1, Handler::GroupError});
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group emptyRoot {
|
||||||
|
OnGroupDone(rootDone)
|
||||||
|
};
|
||||||
|
const Log emptyLog{{-1, Handler::GroupDone}};
|
||||||
|
|
||||||
|
const Group nestedRoot {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 5), readResult),
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 5)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 5))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 4)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 4))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 3)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 3))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 2)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 2))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 1)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 1))
|
||||||
|
},
|
||||||
|
OnGroupDone(rootDone)
|
||||||
|
};
|
||||||
|
const Log nestedLog{{1, Handler::GroupSetup},
|
||||||
|
{2, Handler::GroupSetup},
|
||||||
|
{3, Handler::GroupSetup},
|
||||||
|
{4, Handler::GroupSetup},
|
||||||
|
{5, Handler::GroupSetup},
|
||||||
|
{5, Handler::Setup},
|
||||||
|
{5, Handler::Done},
|
||||||
|
{5, Handler::GroupDone},
|
||||||
|
{4, Handler::GroupDone},
|
||||||
|
{3, Handler::GroupDone},
|
||||||
|
{2, Handler::GroupDone},
|
||||||
|
{1, Handler::GroupDone},
|
||||||
|
{-1, Handler::GroupDone}};
|
||||||
|
|
||||||
|
const Group parallelRoot {
|
||||||
|
parallel,
|
||||||
|
Process(std::bind(setupProcess, _1, 1), readResultAnonymous),
|
||||||
|
Process(std::bind(setupProcess, _1, 2), readResultAnonymous),
|
||||||
|
Process(std::bind(setupProcess, _1, 3), readResultAnonymous),
|
||||||
|
Process(std::bind(setupProcess, _1, 4), readResultAnonymous),
|
||||||
|
Process(std::bind(setupProcess, _1, 5), readResultAnonymous),
|
||||||
|
OnGroupDone(rootDone)
|
||||||
|
};
|
||||||
|
const Log parallelLog{{1, Handler::Setup}, // Setup order is determined in parallel mode
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{3, Handler::Setup},
|
||||||
|
{4, Handler::Setup},
|
||||||
|
{5, Handler::Setup},
|
||||||
|
{-1, Handler::Done}, // Done order isn't determined in parallel mode
|
||||||
|
{-1, Handler::Done},
|
||||||
|
{-1, Handler::Done},
|
||||||
|
{-1, Handler::Done},
|
||||||
|
{-1, Handler::Done},
|
||||||
|
{-1, Handler::GroupDone}}; // Done handlers may come in different order
|
||||||
|
|
||||||
|
const Group sequentialRoot {
|
||||||
|
Process(std::bind(setupProcess, _1, 1), readResult),
|
||||||
|
Process(std::bind(setupProcess, _1, 2), readResult),
|
||||||
|
Process(std::bind(setupProcess, _1, 3), readResult),
|
||||||
|
Process(std::bind(setupProcess, _1, 4), readResult),
|
||||||
|
Process(std::bind(setupProcess, _1, 5), readResult),
|
||||||
|
OnGroupDone(rootDone)
|
||||||
|
};
|
||||||
|
const Group sequentialEncapsulatedRoot {
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 1), readResult)
|
||||||
|
},
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 2), readResult)
|
||||||
|
},
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 3), readResult)
|
||||||
|
},
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 4), readResult)
|
||||||
|
},
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 5), readResult)
|
||||||
|
},
|
||||||
|
OnGroupDone(rootDone)
|
||||||
|
};
|
||||||
|
const Log sequentialLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Done},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{2, Handler::Done},
|
||||||
|
{3, Handler::Setup},
|
||||||
|
{3, Handler::Done},
|
||||||
|
{4, Handler::Setup},
|
||||||
|
{4, Handler::Done},
|
||||||
|
{5, Handler::Setup},
|
||||||
|
{5, Handler::Done},
|
||||||
|
{-1, Handler::GroupDone}};
|
||||||
|
|
||||||
|
const Group sequentialNestedRoot {
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 1), readResult),
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 2), readResult),
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 3), readResult),
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 4), readResult),
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 5), readResult),
|
||||||
|
OnGroupDone(std::bind(groupDone, 5))
|
||||||
|
},
|
||||||
|
OnGroupDone(std::bind(groupDone, 4))
|
||||||
|
},
|
||||||
|
OnGroupDone(std::bind(groupDone, 3))
|
||||||
|
},
|
||||||
|
OnGroupDone(std::bind(groupDone, 2))
|
||||||
|
},
|
||||||
|
OnGroupDone(std::bind(groupDone, 1))
|
||||||
|
},
|
||||||
|
OnGroupDone(rootDone)
|
||||||
|
};
|
||||||
|
const Log sequentialNestedLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Done},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{2, Handler::Done},
|
||||||
|
{3, Handler::Setup},
|
||||||
|
{3, Handler::Done},
|
||||||
|
{4, Handler::Setup},
|
||||||
|
{4, Handler::Done},
|
||||||
|
{5, Handler::Setup},
|
||||||
|
{5, Handler::Done},
|
||||||
|
{5, Handler::GroupDone},
|
||||||
|
{4, Handler::GroupDone},
|
||||||
|
{3, Handler::GroupDone},
|
||||||
|
{2, Handler::GroupDone},
|
||||||
|
{1, Handler::GroupDone},
|
||||||
|
{-1, Handler::GroupDone}};
|
||||||
|
|
||||||
|
const Group sequentialErrorRoot {
|
||||||
|
Process(std::bind(setupProcess, _1, 1), readResult),
|
||||||
|
Process(std::bind(setupProcess, _1, 2), readResult),
|
||||||
|
Process(std::bind(setupCrashProcess, _1, 3), readResult, readError),
|
||||||
|
Process(std::bind(setupProcess, _1, 4), readResult),
|
||||||
|
Process(std::bind(setupProcess, _1, 5), readResult),
|
||||||
|
OnGroupDone(rootDone),
|
||||||
|
OnGroupError(rootError)
|
||||||
|
};
|
||||||
|
const Log sequentialErrorLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Done},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{2, Handler::Done},
|
||||||
|
{3, Handler::Setup},
|
||||||
|
{3, Handler::Error},
|
||||||
|
{-1, Handler::GroupError}};
|
||||||
|
|
||||||
|
const QList<TaskItem> simpleSequence {
|
||||||
|
Process(std::bind(setupProcess, _1, 1), readResult),
|
||||||
|
Process(std::bind(setupCrashProcess, _1, 2), readResult, readError),
|
||||||
|
Process(std::bind(setupProcess, _1, 3), readResult),
|
||||||
|
OnGroupDone(rootDone),
|
||||||
|
OnGroupError(rootError)
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto constructSimpleSequence = [=](const WorkflowPolicy &policy) {
|
||||||
|
return Group {
|
||||||
|
policy,
|
||||||
|
Process(std::bind(setupProcess, _1, 1), readResult),
|
||||||
|
Process(std::bind(setupCrashProcess, _1, 2), readResult, readError),
|
||||||
|
Process(std::bind(setupProcess, _1, 3), readResult),
|
||||||
|
OnGroupDone(rootDone),
|
||||||
|
OnGroupError(rootError)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group stopOnErrorRoot = constructSimpleSequence(stopOnError);
|
||||||
|
const Log stopOnErrorLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Done},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{2, Handler::Error},
|
||||||
|
{-1, Handler::GroupError}};
|
||||||
|
|
||||||
|
const Group continueOnErrorRoot = constructSimpleSequence(continueOnError);
|
||||||
|
const Log continueOnErrorLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Done},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{2, Handler::Error},
|
||||||
|
{3, Handler::Setup},
|
||||||
|
{3, Handler::Done},
|
||||||
|
{-1, Handler::GroupError}};
|
||||||
|
|
||||||
|
const Group stopOnDoneRoot = constructSimpleSequence(stopOnDone);
|
||||||
|
const Log stopOnDoneLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Done},
|
||||||
|
{-1, Handler::GroupDone}};
|
||||||
|
|
||||||
|
const Group continueOnDoneRoot = constructSimpleSequence(continueOnDone);
|
||||||
|
const Log continueOnDoneLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Done},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{2, Handler::Error},
|
||||||
|
{3, Handler::Setup},
|
||||||
|
{3, Handler::Done},
|
||||||
|
{-1, Handler::GroupDone}};
|
||||||
|
|
||||||
|
const Group optionalRoot {
|
||||||
|
optional,
|
||||||
|
Process(std::bind(setupCrashProcess, _1, 1), readResult, readError),
|
||||||
|
Process(std::bind(setupCrashProcess, _1, 2), readResult, readError),
|
||||||
|
OnGroupDone(rootDone),
|
||||||
|
OnGroupError(rootError)
|
||||||
|
};
|
||||||
|
const Log optionalLog{{1, Handler::Setup},
|
||||||
|
{1, Handler::Error},
|
||||||
|
{2, Handler::Setup},
|
||||||
|
{2, Handler::Error},
|
||||||
|
{-1, Handler::GroupDone}};
|
||||||
|
|
||||||
|
QTest::newRow("Empty") << emptyRoot << emptyLog << false << true;
|
||||||
|
QTest::newRow("Nested") << nestedRoot << nestedLog << true << true;
|
||||||
|
QTest::newRow("Parallel") << parallelRoot << parallelLog << true << true;
|
||||||
|
QTest::newRow("Sequential") << sequentialRoot << sequentialLog << true << true;
|
||||||
|
QTest::newRow("SequentialEncapsulated") << sequentialEncapsulatedRoot << sequentialLog << true << true;
|
||||||
|
QTest::newRow("SequentialNested") << sequentialNestedRoot << sequentialNestedLog << true << true;
|
||||||
|
QTest::newRow("SequentialError") << sequentialErrorRoot << sequentialErrorLog << true << false;
|
||||||
|
QTest::newRow("StopOnError") << stopOnErrorRoot << stopOnErrorLog << true << false;
|
||||||
|
QTest::newRow("ContinueOnError") << continueOnErrorRoot << continueOnErrorLog << true << false;
|
||||||
|
QTest::newRow("StopOnDone") << stopOnDoneRoot << stopOnDoneLog << true << true;
|
||||||
|
QTest::newRow("ContinueOnDone") << continueOnDoneRoot << continueOnDoneLog << true << true;
|
||||||
|
QTest::newRow("Optional") << optionalRoot << optionalLog << true << true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_TaskTree::processTree()
|
||||||
|
{
|
||||||
|
m_log = {};
|
||||||
|
using namespace Tasking;
|
||||||
|
|
||||||
|
QFETCH(Group, root);
|
||||||
|
QFETCH(Log, expectedLog);
|
||||||
|
QFETCH(bool, runningAfterStart);
|
||||||
|
QFETCH(bool, success);
|
||||||
|
|
||||||
|
QEventLoop eventLoop;
|
||||||
|
TaskTree processTree(root);
|
||||||
|
int doneCount = 0;
|
||||||
|
int errorCount = 0;
|
||||||
|
connect(&processTree, &TaskTree::done, this, [&doneCount, &eventLoop] { ++doneCount; eventLoop.quit(); });
|
||||||
|
connect(&processTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] { ++errorCount; eventLoop.quit(); });
|
||||||
|
processTree.start();
|
||||||
|
QCOMPARE(processTree.isRunning(), runningAfterStart);
|
||||||
|
|
||||||
|
QTimer timer;
|
||||||
|
connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
|
||||||
|
timer.setInterval(1000);
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
timer.start();
|
||||||
|
eventLoop.exec();
|
||||||
|
|
||||||
|
QVERIFY(!processTree.isRunning());
|
||||||
|
QCOMPARE(m_log, expectedLog);
|
||||||
|
|
||||||
|
const int expectedDoneCount = success ? 1 : 0;
|
||||||
|
const int expectedErrorCount = success ? 0 : 1;
|
||||||
|
QCOMPARE(doneCount, expectedDoneCount);
|
||||||
|
QCOMPARE(errorCount, expectedErrorCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_TaskTree::storage_data()
|
||||||
|
{
|
||||||
|
using namespace Tasking;
|
||||||
|
using namespace std::placeholders;
|
||||||
|
|
||||||
|
QTest::addColumn<Group>("root");
|
||||||
|
QTest::addColumn<std::shared_ptr<Log>>("storageLog");
|
||||||
|
QTest::addColumn<Log>("expectedLog");
|
||||||
|
QTest::addColumn<bool>("runningAfterStart");
|
||||||
|
QTest::addColumn<bool>("success");
|
||||||
|
|
||||||
|
// TODO: Much better approach would be that the TaskTree, when started, creates a
|
||||||
|
// storage dynamically (then Group should be a template with storage type as a
|
||||||
|
// template parameter) and a pointer (or reference) to the storage is being passed
|
||||||
|
// into handlers (? how ?).
|
||||||
|
std::shared_ptr<Log> log(new Log);
|
||||||
|
|
||||||
|
const auto setupProcessHelper = [this, log](QtcProcess &process, const QStringList &args, int processId) {
|
||||||
|
process.setCommand(CommandLine(m_testAppPath, args));
|
||||||
|
process.setProperty(s_processIdProperty, processId);
|
||||||
|
log->append({processId, Handler::Setup});
|
||||||
|
};
|
||||||
|
const auto setupProcess = [setupProcessHelper](QtcProcess &process, int processId) {
|
||||||
|
setupProcessHelper(process, {"-return", "0"}, processId);
|
||||||
|
};
|
||||||
|
const auto readResult = [log](const QtcProcess &process) {
|
||||||
|
const int processId = process.property(s_processIdProperty).toInt();
|
||||||
|
log->append({processId, Handler::Done});
|
||||||
|
};
|
||||||
|
const auto groupSetup = [log](int processId) {
|
||||||
|
log->append({processId, Handler::GroupSetup});
|
||||||
|
};
|
||||||
|
const auto groupDone = [log](int processId) {
|
||||||
|
log->append({processId, Handler::GroupDone});
|
||||||
|
};
|
||||||
|
const auto rootDone = [log] {
|
||||||
|
log->append({-1, Handler::GroupDone});
|
||||||
|
};
|
||||||
|
|
||||||
|
const Group nestedRoot {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Group {
|
||||||
|
Process(std::bind(setupProcess, _1, 5), readResult),
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 5)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 5))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 4)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 4))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 3)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 3))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 2)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 2))
|
||||||
|
},
|
||||||
|
OnGroupSetup(std::bind(groupSetup, 1)),
|
||||||
|
OnGroupDone(std::bind(groupDone, 1))
|
||||||
|
},
|
||||||
|
OnGroupDone(rootDone)
|
||||||
|
};
|
||||||
|
|
||||||
|
const Log nestedLog{{1, Handler::GroupSetup},
|
||||||
|
{2, Handler::GroupSetup},
|
||||||
|
{3, Handler::GroupSetup},
|
||||||
|
{4, Handler::GroupSetup},
|
||||||
|
{5, Handler::GroupSetup},
|
||||||
|
{5, Handler::Setup},
|
||||||
|
{5, Handler::Done},
|
||||||
|
{5, Handler::GroupDone},
|
||||||
|
{4, Handler::GroupDone},
|
||||||
|
{3, Handler::GroupDone},
|
||||||
|
{2, Handler::GroupDone},
|
||||||
|
{1, Handler::GroupDone},
|
||||||
|
{-1, Handler::GroupDone}};
|
||||||
|
QTest::newRow("Nested") << nestedRoot << log << nestedLog << true << true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_TaskTree::storage()
|
||||||
|
{
|
||||||
|
using namespace Tasking;
|
||||||
|
|
||||||
|
QFETCH(Group, root);
|
||||||
|
QFETCH(std::shared_ptr<Log>, storageLog);
|
||||||
|
QFETCH(Log, expectedLog);
|
||||||
|
QFETCH(bool, runningAfterStart);
|
||||||
|
QFETCH(bool, success);
|
||||||
|
|
||||||
|
QEventLoop eventLoop;
|
||||||
|
TaskTree processTree(root);
|
||||||
|
int doneCount = 0;
|
||||||
|
int errorCount = 0;
|
||||||
|
connect(&processTree, &TaskTree::done, this, [&doneCount, &eventLoop] { ++doneCount; eventLoop.quit(); });
|
||||||
|
connect(&processTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] { ++errorCount; eventLoop.quit(); });
|
||||||
|
processTree.start();
|
||||||
|
QCOMPARE(processTree.isRunning(), runningAfterStart);
|
||||||
|
|
||||||
|
QTimer timer;
|
||||||
|
connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
|
||||||
|
timer.setInterval(1000);
|
||||||
|
timer.setSingleShot(true);
|
||||||
|
timer.start();
|
||||||
|
eventLoop.exec();
|
||||||
|
|
||||||
|
QVERIFY(!processTree.isRunning());
|
||||||
|
QCOMPARE(*storageLog, expectedLog);
|
||||||
|
|
||||||
|
const int expectedDoneCount = success ? 1 : 0;
|
||||||
|
const int expectedErrorCount = success ? 0 : 1;
|
||||||
|
QCOMPARE(doneCount, expectedDoneCount);
|
||||||
|
QCOMPARE(errorCount, expectedErrorCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(tst_TaskTree)
|
||||||
|
|
||||||
|
#include "tst_tasktree.moc"
|
||||||
@@ -12,6 +12,7 @@ Project {
|
|||||||
"qtcprocess/qtcprocess.qbs",
|
"qtcprocess/qtcprocess.qbs",
|
||||||
"settings/settings.qbs",
|
"settings/settings.qbs",
|
||||||
"stringutils/stringutils.qbs",
|
"stringutils/stringutils.qbs",
|
||||||
|
"tasktree/tasktree.qbs",
|
||||||
"templateengine/templateengine.qbs",
|
"templateengine/templateengine.qbs",
|
||||||
"treemodel/treemodel.qbs",
|
"treemodel/treemodel.qbs",
|
||||||
"multicursor/multicursor.qbs",
|
"multicursor/multicursor.qbs",
|
||||||
|
|||||||
Reference in New Issue
Block a user