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
|
||||
styledbar.cpp styledbar.h
|
||||
stylehelper.cpp stylehelper.h
|
||||
tasktree.cpp tasktree.h
|
||||
templateengine.cpp templateengine.h
|
||||
temporarydirectory.cpp temporarydirectory.h
|
||||
temporaryfile.cpp temporaryfile.h
|
||||
|
||||
@@ -2091,6 +2091,18 @@ void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &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
|
||||
|
||||
#include "qtcprocess.moc"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "commandline.h"
|
||||
#include "processenums.h"
|
||||
#include "tasktree.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
@@ -202,4 +203,13 @@ public:
|
||||
std::function<Environment(const FilePath &)> systemEnvironmentForBinary;
|
||||
};
|
||||
|
||||
class QTCREATOR_UTILS_EXPORT QtcProcessAdapter : public Tasking::TaskAdapter<QtcProcess>
|
||||
{
|
||||
public:
|
||||
QtcProcessAdapter();
|
||||
void start() final;
|
||||
};
|
||||
|
||||
} // 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",
|
||||
"stylehelper.cpp",
|
||||
"stylehelper.h",
|
||||
"tasktree.cpp",
|
||||
"tasktree.h",
|
||||
"templateengine.cpp",
|
||||
"templateengine.h",
|
||||
"temporarydirectory.cpp",
|
||||
|
||||
@@ -7,6 +7,7 @@ add_subdirectory(qtcprocess)
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(stringutils)
|
||||
add_subdirectory(templateengine)
|
||||
add_subdirectory(tasktree)
|
||||
add_subdirectory(treemodel)
|
||||
add_subdirectory(multicursor)
|
||||
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",
|
||||
"settings/settings.qbs",
|
||||
"stringutils/stringutils.qbs",
|
||||
"tasktree/tasktree.qbs",
|
||||
"templateengine/templateengine.qbs",
|
||||
"treemodel/treemodel.qbs",
|
||||
"multicursor/multicursor.qbs",
|
||||
|
||||
Reference in New Issue
Block a user