blob: 9b327a401e1726dcec17f6e90e129cd778bae8d9 [file] [log] [blame]
//===--- TaskStatus.cpp - Unit tests for the task-status API --------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/Runtime/Concurrency.h"
#include "swift/Basic/STLExtras.h"
#include "gtest/gtest.h"
using namespace swift;
namespace {
template <class T> struct ValueContext;
template <class T>
using InvokeFunctionRef =
llvm::function_ref<void(AsyncTask *task,
ExecutorRef executor,
ValueContext<T> *context)>;
using BodyFunctionRef =
llvm::function_ref<void(AsyncTask *task)>;
template <class Storage> struct ValueContext : AsyncContext {
Storage Value;
InvokeFunctionRef<Storage> StoredInvokeFn;
};
// Disable template argument deduction.
template <class T>
using undeduced =
typename std::enable_if<std::is_same<T, T>::value, T>::type;
template <class T>
SWIFT_CC(swift)
static void simpleTaskInvokeFunction(AsyncTask *task, ExecutorRef executor,
AsyncContext *context) {
auto valueContext = static_cast<ValueContext<T>*>(context);
valueContext->StoredInvokeFn(task, executor, valueContext);
// Destroy the stored value.
valueContext->Value.T::~T();
// Return to finish off the task.
// In a normal situation we'd need to free the context, but here
// we know we're at the top level.
valueContext->ResumeParent(task, executor, valueContext->Parent);
}
template <class T>
static void withSimpleTask(JobFlags flags, T &&value,
undeduced<InvokeFunctionRef<T>> invokeFn,
BodyFunctionRef body) {
auto taskAndContext =
swift_task_create_f(flags, /*parent*/ nullptr,
&simpleTaskInvokeFunction<T>,
sizeof(ValueContext<T>));
auto valueContext =
static_cast<ValueContext<T>*>(taskAndContext.InitialContext);
new (&valueContext->Value) T(std::forward<T>(value));
valueContext->StoredInvokeFn = invokeFn;
// Forward our owning reference to the task into its execution,
// causing it to be destroyed when it completes.
body(taskAndContext.Task);
}
template <class T>
static void withSimpleTask(T &&value,
undeduced<InvokeFunctionRef<T>> invokeFn,
BodyFunctionRef bodyFn) {
withSimpleTask(JobKind::Task, std::forward<T>(value), invokeFn, bodyFn);
}
static ExecutorRef createFakeExecutor(uintptr_t value) {
return ExecutorRef::forDefaultActor(reinterpret_cast<DefaultActor*>(value));
}
} // end anonymous namespace
TEST(TaskStatusTest, basicTasks) {
AsyncTask *createdTask = nullptr;
auto createdExecutor = createFakeExecutor(1234);
bool hasRun = false;
struct Storage { int value; };
withSimpleTask(Storage{47},
[&](AsyncTask *task, ExecutorRef executor,
ValueContext<Storage> *context) {
// The task passed in should be the task we created.
EXPECT_EQ(createdTask, task);
// The executor passed in should be the executor we created.
EXPECT_EQ(createdExecutor, executor);
// We shouldn't have run yet.
EXPECT_FALSE(hasRun);
// The context should've been initialized correctly.
// (This is really just testing our harness, not the runtime.)
EXPECT_EQ(47, context->Value.value);
hasRun = true;
}, [&](AsyncTask *task) {
createdTask = task;
EXPECT_FALSE(hasRun);
createdTask->run(createdExecutor);
EXPECT_TRUE(hasRun);
createdTask = nullptr;
});
EXPECT_TRUE(hasRun);
EXPECT_EQ(nullptr, createdTask);
}
TEST(TaskStatusTest, cancellation_simple) {
struct Storage { int value; };
withSimpleTask(Storage{47},
[&](AsyncTask *task, ExecutorRef executor,
ValueContext<Storage> *context) {
EXPECT_FALSE(task->isCancelled());
swift_task_cancel(task);
EXPECT_TRUE(task->isCancelled());
swift_task_cancel(task);
EXPECT_TRUE(task->isCancelled());
}, [&](AsyncTask *task) {
task->run(createFakeExecutor(1234));
});
}
// Test basic deadline mechanics (other than actually setting up
// something to cancel the task). Also tests adding and removing
// records quite a bit.
TEST(TaskStatusTest, deadline) {
struct Storage { int value; };
withSimpleTask(Storage{47},
[&](AsyncTask *task, ExecutorRef executor,
ValueContext<Storage> *context) {
EXPECT_FALSE(task->isCancelled());
TaskDeadline deadlineOne = { 1234 };
TaskDeadline deadlineTwo = { 2345 };
DeadlineStatusRecord recordOne(deadlineOne);
DeadlineStatusRecord recordTwo(deadlineTwo);
bool result;
NearestTaskDeadline nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::None, nearest.ValueKind);
// Add deadline 1. Check that we haven't been cancelled yet.
result = swift_task_addStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
// There should now be an active deadline.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::Active, nearest.ValueKind);
EXPECT_EQ(deadlineOne, nearest.Value);
// Remove deadline 1. Check that we haven't been cancelled yet.
result = swift_task_removeStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
// There shouldn't be an active deadline anymore.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::None, nearest.ValueKind);
// Add deadline 1, then 2.
result = swift_task_addStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
result = swift_task_addStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
// The nearest deadline should be deadline 1.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::Active, nearest.ValueKind);
EXPECT_EQ(deadlineOne, nearest.Value);
// Remove the deadlines.
result = swift_task_removeStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
result = swift_task_removeStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
// Add deadline 2, then 1s.
result = swift_task_addStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
// In the middle, the nearest deadline should be deadline 2.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::Active, nearest.ValueKind);
EXPECT_EQ(deadlineTwo, nearest.Value);
result = swift_task_addStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
// The nearest deadline should be deadline 1.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::Active, nearest.ValueKind);
EXPECT_EQ(deadlineOne, nearest.Value);
// Remove the deadlines.
result = swift_task_removeStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
result = swift_task_removeStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
// Do the same thing with tryAddStatus.
result = swift_task_tryAddStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
result = swift_task_tryAddStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
// The nearest deadline should be deadline 1.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::Active, nearest.ValueKind);
EXPECT_EQ(deadlineOne, nearest.Value);
result = swift_task_removeStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
result = swift_task_removeStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
// Remove out of order.
result = swift_task_addStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
result = swift_task_addStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
// The nearest deadline should be deadline 1.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::Active, nearest.ValueKind);
EXPECT_EQ(deadlineOne, nearest.Value);
result = swift_task_removeStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
result = swift_task_removeStatusRecord(task, &recordOne);
EXPECT_TRUE(result);
// Add deadline 2, then cancel.
result = swift_task_addStatusRecord(task, &recordTwo);
EXPECT_TRUE(result);
// The nearest deadline should be deadline 2.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::Active, nearest.ValueKind);
EXPECT_EQ(deadlineTwo, nearest.Value);
// Cancel.
swift_task_cancel(task);
EXPECT_TRUE(task->isCancelled());
// We should report already cancelled now.
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::AlreadyCancelled, nearest.ValueKind);
// Add deadline 1.
result = swift_task_addStatusRecord(task, &recordOne);
EXPECT_FALSE(result);
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::AlreadyCancelled, nearest.ValueKind);
result = swift_task_removeStatusRecord(task, &recordOne);
EXPECT_FALSE(result);
result = swift_task_tryAddStatusRecord(task, &recordOne);
EXPECT_FALSE(result);
result = swift_task_removeStatusRecord(task, &recordTwo);
EXPECT_FALSE(result);
nearest = swift_task_getNearestDeadline(task);
EXPECT_EQ(NearestTaskDeadline::AlreadyCancelled, nearest.ValueKind);
EXPECT_TRUE(task->isCancelled());
}, [&](AsyncTask *task) {
task->run(createFakeExecutor(1234));
});
}