blob: c491a4d998c8f0e7bffe1d7c9449a8f4f6367122 [file] [log] [blame]
/*
* Copyright (c) 2018 The Fuchsia Authors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
extern "C" {
#include "../workqueue.h"
} // extern "C"
#include <lib/sync/completion.h>
#include "gtest/gtest.h"
namespace {
struct TestWork {
sync_completion_t entered;
sync_completion_t leaving;
sync_completion_t proceed;
struct work_struct work;
int state;
TestWork();
};
TestWork::TestWork() : state(0) {}
static void handler(struct work_struct* work) {
TestWork* tester = containerof(work, TestWork, work);
tester->state++;
sync_completion_signal(&tester->entered);
sync_completion_wait(&tester->proceed, ZX_TIME_INFINITE);
tester->state++;
sync_completion_signal(&tester->leaving);
}
TEST(Workqueue, JobsInOrder) {
struct workqueue_struct* queue = workqueue_create("MyWork");
TestWork work1;
TestWork work2;
workqueue_init_work(&work1.work, handler);
workqueue_init_work(&work2.work, handler);
workqueue_schedule(queue, &work1.work);
workqueue_schedule(queue, &work2.work);
sync_completion_wait(&work1.entered, ZX_TIME_INFINITE);
EXPECT_EQ(work1.state, 1);
EXPECT_EQ(work2.state, 0);
sync_completion_signal(&work1.proceed);
sync_completion_wait(&work2.entered, ZX_TIME_INFINITE);
EXPECT_EQ(work1.state, 2);
EXPECT_EQ(work2.state, 1);
sync_completion_signal(&work2.proceed);
workqueue_destroy(queue);
}
TEST(Workqueue, ScheduleTwiceIgnored) {
struct workqueue_struct* queue = workqueue_create("MyWork");
TestWork work1;
TestWork work2;
workqueue_init_work(&work1.work, handler);
workqueue_init_work(&work2.work, handler);
// Test for both current and pending work
workqueue_schedule(queue, &work1.work);
zx_nanosleep(zx_deadline_after(ZX_MSEC(50)));
workqueue_schedule(queue, &work1.work);
workqueue_schedule(queue, &work2.work);
workqueue_schedule(queue, &work2.work);
sync_completion_signal(&work1.proceed);
sync_completion_signal(&work2.proceed);
workqueue_destroy(queue);
EXPECT_EQ(work1.state, 2);
EXPECT_EQ(work2.state, 2);
}
TEST(Workqueue, DefaultQueueWorks) {
TestWork work1;
workqueue_init_work(&work1.work, handler);
workqueue_schedule_default(&work1.work);
sync_completion_wait(&work1.entered, ZX_TIME_INFINITE);
EXPECT_EQ(work1.state, 1);
sync_completion_signal(&work1.proceed);
workqueue_flush_default();
}
// Upon canceling a job that has not started yet, it should never run, and the cancel should
// return without blocking.
TEST(Workqueue, CancelPending) {
struct workqueue_struct* queue = workqueue_create("MyWork");
TestWork work1;
TestWork work2;
TestWork work3;
workqueue_init_work(&work1.work, handler);
workqueue_init_work(&work2.work, handler);
workqueue_init_work(&work3.work, handler);
workqueue_schedule(queue, &work1.work);
workqueue_schedule(queue, &work2.work);
workqueue_schedule(queue, &work3.work);
sync_completion_wait(&work1.entered, ZX_TIME_INFINITE);
EXPECT_EQ(work1.state, 1);
EXPECT_EQ(work2.state, 0);
workqueue_cancel_work(&work2.work);
sync_completion_signal(&work1.proceed);
sync_completion_wait(&work3.entered, ZX_TIME_INFINITE);
EXPECT_EQ(work1.state, 2);
EXPECT_EQ(work2.state, 0);
EXPECT_EQ(work3.state, 1);
sync_completion_signal(&work3.proceed);
workqueue_destroy(queue);
}
struct WorkCanceler {
struct work_struct work;
TestWork* target;
sync_completion_t leaving;
int state;
WorkCanceler();
};
WorkCanceler::WorkCanceler() : state(0) {}
static void cancel_handler(struct work_struct* work) {
WorkCanceler* canceler = containerof(work, WorkCanceler, work);
workqueue_cancel_work(&canceler->target->work);
canceler->state = 2;
sync_completion_signal(&canceler->leaving);
}
// Upon canceling a job that is in progress, the canceler should block until the job completes.
// Subsequent jobs should also complete.
TEST(Workqueue, CancelCurrent) {
struct workqueue_struct* queue = workqueue_create("MyWork");
TestWork work1;
TestWork work2;
WorkCanceler canceler;
canceler.target = &work1;
workqueue_init_work(&work1.work, handler);
workqueue_init_work(&work2.work, handler);
workqueue_init_work(&canceler.work, cancel_handler);
workqueue_schedule(queue, &work1.work);
workqueue_schedule(queue, &work2.work);
sync_completion_wait(&work1.entered, ZX_TIME_INFINITE);
EXPECT_EQ(work1.state, 1);
EXPECT_EQ(work2.state, 0);
workqueue_schedule_default(&canceler.work);
// If this timeout is too short and the canceler hasn't entered yet, it may cause a false pass,
// but not a flaky fail.
zx_nanosleep(zx_deadline_after(ZX_MSEC(50)));
EXPECT_EQ(work1.state, 1);
EXPECT_EQ(canceler.state, 0);
sync_completion_signal(&work1.proceed);
sync_completion_wait(&work2.entered, ZX_TIME_INFINITE);
EXPECT_EQ(work1.state, 2);
EXPECT_EQ(work2.state, 1);
sync_completion_wait(&canceler.leaving, ZX_TIME_INFINITE);
sync_completion_signal(&work2.proceed);
workqueue_destroy(queue);
}
} // namespace