blob: 961b070fbe6ed04b825d1ae0518151f0a8c63f3b [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_thread.h"
#include <atomic>
#include <vector>
#include "starboard/shared/starboard/player/job_queue.h"
#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.
constexpr SbTime kPrecision = kSbTimeMillisecond;
void ExecutePendingJobs(JobThread* job_thread) {
job_thread->ScheduleAndWait([]() {});
}
TEST(JobThreadTest, ScheduledJobsAreExecutedInOrder) {
std::vector<int> values;
JobThread job_thread{"JobThreadTests"};
job_thread.Schedule([&]() { values.push_back(1); });
job_thread.Schedule([&]() { values.push_back(2); });
job_thread.Schedule([&]() { values.push_back(3); });
job_thread.Schedule([&]() { values.push_back(4); }, 1 * kPrecision);
job_thread.Schedule([&]() { values.push_back(5); }, 1 * kPrecision);
job_thread.Schedule([&]() { values.push_back(6); }, 1 * kPrecision);
job_thread.Schedule([&]() { values.push_back(7); }, 2 * kPrecision);
job_thread.Schedule([&]() { values.push_back(8); }, 3 * kPrecision);
// Sleep past the last scheduled job.
SbThreadSleep(4 * kPrecision);
ExecutePendingJobs(&job_thread);
EXPECT_THAT(values, ElementsAre(1, 2, 3, 4, 5, 6, 7, 8));
}
TEST(JobThreadTest, ScheduleAndWaitWaits) {
SbTimeMonotonic start = SbTimeGetMonotonicNow();
std::atomic_bool job_1 = {false};
JobThread job_thread{"JobThreadTests"};
job_thread.ScheduleAndWait([&]() {
SbThreadSleep(1 * kPrecision);
job_1 = true;
});
// Verify that the job ran and that it took at least as long as it slept.
EXPECT_TRUE(job_1);
EXPECT_GE(SbTimeGetMonotonicNow() - start, 1 * kPrecision);
}
TEST(JobThreadTest, ScheduledJobsShouldNotExecuteAfterGoingOutOfScope) {
std::atomic_int counter = {0};
{
JobThread job_thread{"JobThreadTests"};
std::function<void()> job = [&]() {
counter++;
job_thread.Schedule(job, 2 * kPrecision);
};
job_thread.Schedule(job);
// Wait for the job to run at least once and reschedule itself.
SbThreadSleep(1 * kPrecision);
ExecutePendingJobs(&job_thread);
}
int end_value = counter;
EXPECT_GE(counter, 1);
// Sleep past two more (potential) executions and verify there were none.
SbThreadSleep(4 * kPrecision);
EXPECT_EQ(counter, end_value);
}
TEST(JobThreadTest, CanceledJobsAreCanceled) {
std::atomic_int counter_1 = {0}, counter_2 = {0};
JobQueue::JobToken job_token_1, job_token_2;
JobThread job_thread{"JobThreadTests"};
std::function<void()> job_1 = [&]() {
counter_1++;
job_token_1 = job_thread.Schedule(job_1);
};
std::function<void()> job_2 = [&]() {
counter_2++;
job_token_2 = job_thread.Schedule(job_2);
};
job_token_1 = job_thread.Schedule(job_1);
job_token_2 = job_thread.Schedule(job_2);
// Wait for the scheduled jobs to at least run once.
ExecutePendingJobs(&job_thread);
// Cancel job 1 and grab the current counter values.
job_thread.ScheduleAndWait(
[&]() { job_thread.RemoveJobByToken(job_token_1); });
int checkpoint_1 = counter_1;
int checkpoint_2 = counter_2;
// Sleep and wait for pending jobs to run.
SbThreadSleep(1 * kPrecision);
ExecutePendingJobs(&job_thread);
// Job 1 should not have run again.
EXPECT_EQ(counter_1, checkpoint_1);
// Job 2 should have continued running.
EXPECT_GT(counter_2, checkpoint_2);
// Cancel job 2 to avoid it scheduling itself during destruction.
job_thread.ScheduleAndWait(
[&]() { job_thread.RemoveJobByToken(job_token_2); });
}
TEST(JobThreadTest, QueueBelongsToCorrectThread) {
JobThread job_thread{"JobThreadTests"};
JobQueue job_queue;
bool belongs_to_job_thread = false;
bool belongs_to_main_thread = false;
// Schedule in JobQueue owned by job thread.
job_thread.ScheduleAndWait([&]() {
belongs_to_job_thread = job_thread.BelongsToCurrentThread();
belongs_to_main_thread = job_queue.BelongsToCurrentThread();
});
EXPECT_TRUE(belongs_to_job_thread);
EXPECT_FALSE(belongs_to_main_thread);
belongs_to_job_thread = belongs_to_main_thread = false;
// Schedule in JobQueue owned by main thread.
job_queue.Schedule([&]() {
belongs_to_job_thread = job_thread.BelongsToCurrentThread();
belongs_to_main_thread = job_queue.BelongsToCurrentThread();
});
job_queue.RunUntilIdle();
EXPECT_FALSE(belongs_to_job_thread);
EXPECT_TRUE(belongs_to_main_thread);
}
} // namespace
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard