forked from qt-creator/qt-creator
TaskTree: Introduce Storage item
The Storage item makes it possible to define the whole subtree as a self-contained, full-functional recipe, without a need for passing (together with recipe) a one-use only (disposable) pointer to storage object. That's the last closing element of making the idea of pure, value-based recipe real. It makes the TaskTree machinery ultimately powerful. Change-Id: Icd81bdd3e94251e8b241b2b550957d566fa4ab75 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -117,6 +117,9 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
|
|||||||
if (child.m_groupHandler.m_dynamicSetupHandler)
|
if (child.m_groupHandler.m_dynamicSetupHandler)
|
||||||
m_groupHandler.m_dynamicSetupHandler = child.m_groupHandler.m_dynamicSetupHandler;
|
m_groupHandler.m_dynamicSetupHandler = child.m_groupHandler.m_dynamicSetupHandler;
|
||||||
break;
|
break;
|
||||||
|
case Type::Storage:
|
||||||
|
m_storageList.append(child.m_storageList);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,11 +147,18 @@ public:
|
|||||||
void resetSuccessBit();
|
void resetSuccessBit();
|
||||||
void updateSuccessBit(bool success);
|
void updateSuccessBit(bool success);
|
||||||
|
|
||||||
|
void createStorages();
|
||||||
|
void deleteStorages();
|
||||||
|
void activateStorages();
|
||||||
|
void deactivateStorages();
|
||||||
|
|
||||||
TaskTreePrivate *m_taskTreePrivate = nullptr;
|
TaskTreePrivate *m_taskTreePrivate = nullptr;
|
||||||
TaskContainer *m_parentContainer = nullptr;
|
TaskContainer *m_parentContainer = nullptr;
|
||||||
const ExecuteMode m_executeMode = ExecuteMode::Parallel;
|
const ExecuteMode m_executeMode = ExecuteMode::Parallel;
|
||||||
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
||||||
const TaskItem::GroupHandler m_groupHandler;
|
const TaskItem::GroupHandler m_groupHandler;
|
||||||
|
QList<TreeStorageBase> m_storageList;
|
||||||
|
QList<int> m_storageIdList;
|
||||||
int m_taskCount = 0;
|
int m_taskCount = 0;
|
||||||
GroupConfig m_groupConfig;
|
GroupConfig m_groupConfig;
|
||||||
QList<TaskNode *> m_children;
|
QList<TaskNode *> m_children;
|
||||||
@@ -157,6 +167,22 @@ public:
|
|||||||
bool m_successBit = true;
|
bool m_successBit = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class StorageActivator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StorageActivator(TaskContainer &container)
|
||||||
|
: m_container(container)
|
||||||
|
{
|
||||||
|
m_container.activateStorages();
|
||||||
|
}
|
||||||
|
~StorageActivator()
|
||||||
|
{
|
||||||
|
m_container.deactivateStorages();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
TaskContainer &m_container;
|
||||||
|
};
|
||||||
|
|
||||||
class TaskNode : public QObject
|
class TaskNode : public QObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -244,6 +270,7 @@ TaskContainer::TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *pa
|
|||||||
, m_executeMode(task.executeMode())
|
, m_executeMode(task.executeMode())
|
||||||
, m_workflowPolicy(task.workflowPolicy())
|
, m_workflowPolicy(task.workflowPolicy())
|
||||||
, m_groupHandler(task.groupHandler())
|
, m_groupHandler(task.groupHandler())
|
||||||
|
, m_storageList(task.storageList())
|
||||||
{
|
{
|
||||||
const QList<TaskItem> &children = task.children();
|
const QList<TaskItem> &children = task.children();
|
||||||
for (const TaskItem &child : children) {
|
for (const TaskItem &child : children) {
|
||||||
@@ -264,14 +291,18 @@ void TaskContainer::start()
|
|||||||
m_groupConfig = {};
|
m_groupConfig = {};
|
||||||
m_selectedChildren.clear();
|
m_selectedChildren.clear();
|
||||||
|
|
||||||
if (m_groupHandler.m_setupHandler) {
|
createStorages();
|
||||||
GuardLocker locker(m_taskTreePrivate->m_guard);
|
{
|
||||||
m_groupHandler.m_setupHandler();
|
StorageActivator activator(*this);
|
||||||
}
|
if (m_groupHandler.m_setupHandler) {
|
||||||
|
GuardLocker locker(m_taskTreePrivate->m_guard);
|
||||||
|
m_groupHandler.m_setupHandler();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_groupHandler.m_dynamicSetupHandler) {
|
if (m_groupHandler.m_dynamicSetupHandler) {
|
||||||
GuardLocker locker(m_taskTreePrivate->m_guard);
|
GuardLocker locker(m_taskTreePrivate->m_guard);
|
||||||
m_groupConfig = m_groupHandler.m_dynamicSetupHandler();
|
m_groupConfig = m_groupHandler.m_dynamicSetupHandler();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_groupConfig.action == GroupAction::StopWithDone || m_groupConfig.action == GroupAction::StopWithError) {
|
if (m_groupConfig.action == GroupAction::StopWithDone || m_groupConfig.action == GroupAction::StopWithError) {
|
||||||
@@ -374,13 +405,17 @@ void TaskContainer::invokeEndHandler(bool success, bool propagateToParent)
|
|||||||
{
|
{
|
||||||
m_currentIndex = -1;
|
m_currentIndex = -1;
|
||||||
m_successBit = success;
|
m_successBit = success;
|
||||||
if (success && m_groupHandler.m_doneHandler) {
|
{
|
||||||
GuardLocker locker(m_taskTreePrivate->m_guard);
|
StorageActivator activator(*this);
|
||||||
m_groupHandler.m_doneHandler();
|
if (success && m_groupHandler.m_doneHandler) {
|
||||||
} else if (!success && m_groupHandler.m_errorHandler) {
|
GuardLocker locker(m_taskTreePrivate->m_guard);
|
||||||
GuardLocker locker(m_taskTreePrivate->m_guard);
|
m_groupHandler.m_doneHandler();
|
||||||
m_groupHandler.m_errorHandler();
|
} else if (!success && m_groupHandler.m_errorHandler) {
|
||||||
|
GuardLocker locker(m_taskTreePrivate->m_guard);
|
||||||
|
m_groupHandler.m_errorHandler();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
deleteStorages();
|
||||||
|
|
||||||
if (!propagateToParent)
|
if (!propagateToParent)
|
||||||
return;
|
return;
|
||||||
@@ -420,6 +455,46 @@ void TaskContainer::updateSuccessBit(bool success)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TaskContainer::createStorages()
|
||||||
|
{
|
||||||
|
// TODO: Don't create new storage for already created storages with the same shared pointer.
|
||||||
|
|
||||||
|
for (int i = 0; i < m_storageList.size(); ++i)
|
||||||
|
m_storageIdList << m_storageList[i].createStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::deleteStorages()
|
||||||
|
{
|
||||||
|
// TODO: Do the opposite
|
||||||
|
|
||||||
|
for (int i = 0; i < m_storageList.size(); ++i) // iterate in reverse order?
|
||||||
|
m_storageList[i].deleteStorage(m_storageIdList.value(i));
|
||||||
|
|
||||||
|
m_storageIdList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::activateStorages()
|
||||||
|
{
|
||||||
|
// TODO: check if the same shared storage was already activated. Don't activate recursively.
|
||||||
|
|
||||||
|
if (m_parentContainer)
|
||||||
|
m_parentContainer->activateStorages();
|
||||||
|
|
||||||
|
for (int i = 0; i < m_storageList.size(); ++i)
|
||||||
|
m_storageList[i].activateStorage(m_storageIdList.value(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskContainer::deactivateStorages()
|
||||||
|
{
|
||||||
|
// TODO: Do the opposite
|
||||||
|
|
||||||
|
for (int i = 0; i < m_storageList.size(); ++i) // iterate in reverse order?
|
||||||
|
m_storageList[i].activateStorage(0);
|
||||||
|
|
||||||
|
if (m_parentContainer)
|
||||||
|
m_parentContainer->deactivateStorages();
|
||||||
|
}
|
||||||
|
|
||||||
bool TaskNode::start()
|
bool TaskNode::start()
|
||||||
{
|
{
|
||||||
if (!isTask()) {
|
if (!isTask()) {
|
||||||
@@ -428,16 +503,20 @@ bool TaskNode::start()
|
|||||||
}
|
}
|
||||||
m_task.reset(m_taskHandler.m_createHandler());
|
m_task.reset(m_taskHandler.m_createHandler());
|
||||||
{
|
{
|
||||||
|
StorageActivator activator(m_container);
|
||||||
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
||||||
m_taskHandler.m_setupHandler(*m_task.get());
|
m_taskHandler.m_setupHandler(*m_task.get());
|
||||||
}
|
}
|
||||||
connect(m_task.get(), &TaskInterface::done, this, [this](bool success) {
|
connect(m_task.get(), &TaskInterface::done, this, [this](bool success) {
|
||||||
if (success && m_taskHandler.m_doneHandler) {
|
{
|
||||||
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
StorageActivator activator(m_container);
|
||||||
m_taskHandler.m_doneHandler(*m_task.get());
|
if (success && m_taskHandler.m_doneHandler) {
|
||||||
} else if (!success && m_taskHandler.m_errorHandler) {
|
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
||||||
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
m_taskHandler.m_doneHandler(*m_task.get());
|
||||||
m_taskHandler.m_errorHandler(*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_container.m_taskTreePrivate->advanceProgress(1);
|
m_container.m_taskTreePrivate->advanceProgress(1);
|
||||||
|
|
||||||
@@ -465,6 +544,7 @@ void TaskNode::stop()
|
|||||||
// TODO: cancelHandler?
|
// TODO: cancelHandler?
|
||||||
// TODO: call TaskInterface::stop() ?
|
// TODO: call TaskInterface::stop() ?
|
||||||
if (m_taskHandler.m_errorHandler) {
|
if (m_taskHandler.m_errorHandler) {
|
||||||
|
StorageActivator activator(m_container);
|
||||||
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
|
||||||
m_taskHandler.m_errorHandler(*m_task.get());
|
m_taskHandler.m_errorHandler(*m_task.get());
|
||||||
}
|
}
|
||||||
|
@@ -143,15 +143,16 @@ public:
|
|||||||
TaskHandler taskHandler() const { return m_taskHandler; }
|
TaskHandler taskHandler() const { return m_taskHandler; }
|
||||||
GroupHandler groupHandler() const { return m_groupHandler; }
|
GroupHandler groupHandler() const { return m_groupHandler; }
|
||||||
QList<TaskItem> children() const { return m_children; }
|
QList<TaskItem> children() const { return m_children; }
|
||||||
|
QList<TreeStorageBase> storageList() const { return m_storageList; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum class Type {
|
enum class Type {
|
||||||
Group,
|
Group,
|
||||||
|
Storage,
|
||||||
Mode,
|
Mode,
|
||||||
Policy,
|
Policy,
|
||||||
TaskHandler,
|
TaskHandler,
|
||||||
GroupHandler
|
GroupHandler
|
||||||
// TODO: Add Cond type (with CondHandler and True and False branches)?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TaskItem() = default;
|
TaskItem() = default;
|
||||||
@@ -167,6 +168,9 @@ protected:
|
|||||||
TaskItem(const GroupHandler &handler)
|
TaskItem(const GroupHandler &handler)
|
||||||
: m_type(Type::GroupHandler)
|
: m_type(Type::GroupHandler)
|
||||||
, m_groupHandler(handler) {}
|
, m_groupHandler(handler) {}
|
||||||
|
TaskItem(const TreeStorageBase &storage)
|
||||||
|
: m_type(Type::Storage)
|
||||||
|
, m_storageList{storage} {}
|
||||||
void addChildren(const QList<TaskItem> &children);
|
void addChildren(const QList<TaskItem> &children);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -175,6 +179,7 @@ private:
|
|||||||
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
||||||
TaskHandler m_taskHandler;
|
TaskHandler m_taskHandler;
|
||||||
GroupHandler m_groupHandler;
|
GroupHandler m_groupHandler;
|
||||||
|
QList<TreeStorageBase> m_storageList;
|
||||||
QList<TaskItem> m_children;
|
QList<TaskItem> m_children;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -185,6 +190,12 @@ public:
|
|||||||
Group(std::initializer_list<TaskItem> children) { addChildren(children); }
|
Group(std::initializer_list<TaskItem> children) { addChildren(children); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT Storage : public TaskItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
|
||||||
|
};
|
||||||
|
|
||||||
class QTCREATOR_UTILS_EXPORT Execute : public TaskItem
|
class QTCREATOR_UTILS_EXPORT Execute : public TaskItem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@@ -496,46 +496,72 @@ void tst_TaskTree::processTree()
|
|||||||
QCOMPARE(errorCount, expectedErrorCount);
|
QCOMPARE(errorCount, expectedErrorCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CustomStorage
|
||||||
|
{
|
||||||
|
CustomStorage() { ++s_count; }
|
||||||
|
~CustomStorage() { --s_count; }
|
||||||
|
Log m_log;
|
||||||
|
static int instanceCount() { return s_count; }
|
||||||
|
private:
|
||||||
|
static int s_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
int CustomStorage::s_count = 0;
|
||||||
|
static Log s_log;
|
||||||
|
|
||||||
void tst_TaskTree::storage_data()
|
void tst_TaskTree::storage_data()
|
||||||
{
|
{
|
||||||
using namespace Tasking;
|
using namespace Tasking;
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
|
|
||||||
QTest::addColumn<Group>("root");
|
QTest::addColumn<Group>("root");
|
||||||
QTest::addColumn<std::shared_ptr<Log>>("storageLog");
|
QTest::addColumn<TreeStorage<CustomStorage>>("storageLog");
|
||||||
QTest::addColumn<Log>("expectedLog");
|
QTest::addColumn<Log>("expectedLog");
|
||||||
QTest::addColumn<bool>("runningAfterStart");
|
QTest::addColumn<bool>("runningAfterStart");
|
||||||
QTest::addColumn<bool>("success");
|
QTest::addColumn<bool>("success");
|
||||||
|
|
||||||
// TODO: Much better approach would be that the TaskTree, when started, creates a
|
TreeStorage<CustomStorage> storageLog;
|
||||||
// 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) {
|
const auto setupProcessHelper = [storageLog, testAppPath = m_testAppPath]
|
||||||
process.setCommand(CommandLine(m_testAppPath, args));
|
(QtcProcess &process, const QStringList &args, int processId) {
|
||||||
|
process.setCommand(CommandLine(testAppPath, args));
|
||||||
process.setProperty(s_processIdProperty, processId);
|
process.setProperty(s_processIdProperty, processId);
|
||||||
log->append({processId, Handler::Setup});
|
storageLog->m_log.append({processId, Handler::Setup});
|
||||||
};
|
};
|
||||||
const auto setupProcess = [setupProcessHelper](QtcProcess &process, int processId) {
|
const auto setupProcess = [setupProcessHelper](QtcProcess &process, int processId) {
|
||||||
setupProcessHelper(process, {"-return", "0"}, processId);
|
setupProcessHelper(process, {"-return", "0"}, processId);
|
||||||
};
|
};
|
||||||
const auto readResult = [log](const QtcProcess &process) {
|
const auto readResult = [storageLog](const QtcProcess &process) {
|
||||||
const int processId = process.property(s_processIdProperty).toInt();
|
const int processId = process.property(s_processIdProperty).toInt();
|
||||||
log->append({processId, Handler::Done});
|
storageLog->m_log.append({processId, Handler::Done});
|
||||||
};
|
};
|
||||||
const auto groupSetup = [log](int processId) {
|
const auto groupSetup = [storageLog](int processId) {
|
||||||
log->append({processId, Handler::GroupSetup});
|
storageLog->m_log.append({processId, Handler::GroupSetup});
|
||||||
};
|
};
|
||||||
const auto groupDone = [log](int processId) {
|
const auto groupDone = [storageLog](int processId) {
|
||||||
log->append({processId, Handler::GroupDone});
|
storageLog->m_log.append({processId, Handler::GroupDone});
|
||||||
};
|
};
|
||||||
const auto rootDone = [log] {
|
const auto rootDone = [storageLog] {
|
||||||
log->append({-1, Handler::GroupDone});
|
storageLog->m_log.append({-1, Handler::GroupDone});
|
||||||
|
s_log = storageLog->m_log;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Group nestedRoot {
|
const Log expectedLog{{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 root {
|
||||||
|
Storage(storageLog),
|
||||||
Group {
|
Group {
|
||||||
Group {
|
Group {
|
||||||
Group {
|
Group {
|
||||||
@@ -560,20 +586,7 @@ void tst_TaskTree::storage_data()
|
|||||||
OnGroupDone(rootDone)
|
OnGroupDone(rootDone)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Log nestedLog{{1, Handler::GroupSetup},
|
QTest::newRow("Storage") << root << storageLog << expectedLog << true << true;
|
||||||
{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()
|
void tst_TaskTree::storage()
|
||||||
@@ -581,11 +594,16 @@ void tst_TaskTree::storage()
|
|||||||
using namespace Tasking;
|
using namespace Tasking;
|
||||||
|
|
||||||
QFETCH(Group, root);
|
QFETCH(Group, root);
|
||||||
QFETCH(std::shared_ptr<Log>, storageLog);
|
QFETCH(TreeStorage<CustomStorage>, storageLog);
|
||||||
QFETCH(Log, expectedLog);
|
QFETCH(Log, expectedLog);
|
||||||
QFETCH(bool, runningAfterStart);
|
QFETCH(bool, runningAfterStart);
|
||||||
QFETCH(bool, success);
|
QFETCH(bool, success);
|
||||||
|
|
||||||
|
s_log.clear();
|
||||||
|
|
||||||
|
QVERIFY(storageLog.isValid());
|
||||||
|
QCOMPARE(CustomStorage::instanceCount(), 0);
|
||||||
|
|
||||||
QEventLoop eventLoop;
|
QEventLoop eventLoop;
|
||||||
TaskTree processTree(root);
|
TaskTree processTree(root);
|
||||||
int doneCount = 0;
|
int doneCount = 0;
|
||||||
@@ -593,6 +611,7 @@ void tst_TaskTree::storage()
|
|||||||
connect(&processTree, &TaskTree::done, this, [&doneCount, &eventLoop] { ++doneCount; eventLoop.quit(); });
|
connect(&processTree, &TaskTree::done, this, [&doneCount, &eventLoop] { ++doneCount; eventLoop.quit(); });
|
||||||
connect(&processTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] { ++errorCount; eventLoop.quit(); });
|
connect(&processTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] { ++errorCount; eventLoop.quit(); });
|
||||||
processTree.start();
|
processTree.start();
|
||||||
|
QCOMPARE(CustomStorage::instanceCount(), 1);
|
||||||
QCOMPARE(processTree.isRunning(), runningAfterStart);
|
QCOMPARE(processTree.isRunning(), runningAfterStart);
|
||||||
|
|
||||||
QTimer timer;
|
QTimer timer;
|
||||||
@@ -603,7 +622,8 @@ void tst_TaskTree::storage()
|
|||||||
eventLoop.exec();
|
eventLoop.exec();
|
||||||
|
|
||||||
QVERIFY(!processTree.isRunning());
|
QVERIFY(!processTree.isRunning());
|
||||||
QCOMPARE(*storageLog, expectedLog);
|
QCOMPARE(s_log, expectedLog);
|
||||||
|
QCOMPARE(CustomStorage::instanceCount(), 0);
|
||||||
|
|
||||||
const int expectedDoneCount = success ? 1 : 0;
|
const int expectedDoneCount = success ? 1 : 0;
|
||||||
const int expectedErrorCount = success ? 0 : 1;
|
const int expectedErrorCount = success ? 0 : 1;
|
||||||
|
Reference in New Issue
Block a user