blob: 53578cf68adf381f436db3090365bf5801dc3552 [file] [log] [blame]
// Copyright 2021 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/player/job_queue.h"
#include <atomic>
#include <vector>
#include "starboard/thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace {
using ::testing::ElementsAre;
// Require at least millisecond-level precision.
const SbTime kPrecision = kSbTimeMillisecond;
class JobQueueTest
: public ::testing::Test,
protected ::starboard::shared::starboard::player::JobQueue::JobOwner {
public:
JobQueueTest() : JobOwner(kDetached) {}
~JobQueueTest() {}
// Create a JobQueue for use on the current thread.
JobQueue job_queue_;
};
TEST_F(JobQueueTest, OwnedScheduledJobsAreExecutedInOrder) {
AttachToCurrentThread();
std::vector<int> values;
Schedule([&]() { values.push_back(1); });
Schedule([&]() { values.push_back(2); });
Schedule([&]() { values.push_back(3); });
Schedule([&]() { values.push_back(4); }, 1 * kPrecision);
Schedule([&]() { values.push_back(5); }, 1 * kPrecision);
Schedule([&]() { values.push_back(6); }, 1 * kPrecision);
Schedule([&]() { values.push_back(7); }, 2 * kPrecision);
Schedule([&]() { values.push_back(8); }, 3 * kPrecision);
// Sleep past the last scheduled job.
SbThreadSleep(4 * kPrecision);
job_queue_.RunUntilIdle();
EXPECT_THAT(values, ElementsAre(1, 2, 3, 4, 5, 6, 7, 8));
DetachFromCurrentThread();
}
TEST_F(JobQueueTest, OwnedJobsAreRemovedWhenOwnerGoesOutOfScope) {
std::atomic_bool job_1 = {false}, job_2 = {false};
{
JobQueue::JobOwner job_owner(&job_queue_);
job_owner.Schedule([&]() { job_1 = true; });
job_queue_.Schedule([&]() { job_2 = true; });
}
// Execute pending jobs.
job_queue_.RunUntilIdle();
// Job 1 was canceled due to job_owner going out of scope.
EXPECT_FALSE(job_1);
// Job 2 should have run as it was not owned by job_owner.
EXPECT_TRUE(job_2);
}
TEST_F(JobQueueTest, CancelPendingJobsCancelsPendingJobs) {
AttachToCurrentThread();
std::atomic_bool job_1 = {false}, job_2 = {false};
Schedule([&]() { job_1 = true; });
job_queue_.Schedule([&]() { job_2 = true; });
// Cancel the pending owned job (job 1).
CancelPendingJobs();
// Execute any remaining pending jobs.
job_queue_.RunUntilIdle();
// Job 1 was owned by |this| and should have been canceled.
EXPECT_FALSE(job_1);
// Job 2 should have run.
EXPECT_TRUE(job_2);
DetachFromCurrentThread();
}
TEST_F(JobQueueTest, RemovedJobsAreRemoved) {
std::atomic_bool job_1 = {false}, job_2 = {false};
auto job_token_1 = job_queue_.Schedule([&]() { job_1 = true; });
auto job_token_2 = job_queue_.Schedule([&]() { job_2 = true; });
// Cancel job 1.
job_queue_.RemoveJobByToken(job_token_1);
job_queue_.RunUntilIdle();
// Job 1 should have been canceled.
EXPECT_FALSE(job_1);
EXPECT_TRUE(job_2);
// Should be a no-op since job 2 already ran.
job_queue_.RemoveJobByToken(job_token_2);
}
TEST_F(JobQueueTest, RunUntilStoppedExecutesAllRemainingJobs) {
SbTimeMonotonic start = SbTimeGetMonotonicNow();
std::atomic_bool job_1 = {false}, job_2 = {false}, job_3 = {false};
job_queue_.Schedule([&job_1]() { job_1 = true; });
job_queue_.Schedule([&job_2]() { job_2 = true; }, 1 * kPrecision);
job_queue_.Schedule([&job_3]() { job_3 = true; }, 2 * kPrecision);
job_queue_.Schedule([&]() { job_queue_.StopSoon(); }, 3 * kPrecision);
job_queue_.RunUntilIdle();
// Job 1 should have been executed.
EXPECT_TRUE(job_1);
job_queue_.RunUntilStopped();
// All other pending jobs should have run.
EXPECT_TRUE(job_2);
EXPECT_TRUE(job_3);
// Time passed should at least be as long as the delay of the last job.
EXPECT_GE(SbTimeGetMonotonicNow() - start, 3 * kPrecision);
}
TEST_F(JobQueueTest, JobsAreMovedAndNotCopied) {
struct MoveMe {
public:
MoveMe() {}
MoveMe(MoveMe const& other) {
copied = true;
moved = other.moved;
}
MoveMe(MoveMe&& other) {
copied = other.copied;
moved = true;
}
bool copied = false;
bool moved = false;
};
std::atomic_bool moved = {false}, copied = {false};
MoveMe move_me;
auto job = [ move_me = std::move(move_me), &moved, &copied ]() {
moved = move_me.moved;
copied = move_me.copied;
};
job_queue_.Schedule(std::move(job));
job_queue_.RunUntilIdle();
EXPECT_FALSE(copied);
EXPECT_TRUE(moved);
}
TEST_F(JobQueueTest, QueueBelongsToCorrectThread) {
EXPECT_EQ(&job_queue_, JobQueue::current());
EXPECT_TRUE(job_queue_.BelongsToCurrentThread());
}
} // namespace
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard