transcoding: add job scheduler and unit tests
bug: 145233472
Change-Id: Id0244ff553e9fa963ccc7623cc6198dfd9db2564
diff --git a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
index d58af4e..2e49f32 100644
--- a/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
+++ b/media/libmediatranscoding/tests/AdjustableMaxPriorityQueue_tests.cpp
@@ -36,7 +36,7 @@
namespace android {
class IntUniquePtrComp {
- public:
+public:
bool operator()(const std::unique_ptr<int>& lhs, const std::unique_ptr<int>& rhs) const {
return *lhs < *rhs;
}
@@ -233,7 +233,7 @@
// The job is arranging according to priority with highest priority comes first.
// For the job with the same priority, the job with early createTime will come first.
class TranscodingJobComp {
- public:
+ public:
bool operator()(const std::unique_ptr<TranscodingJob>& lhs,
const std::unique_ptr<TranscodingJob>& rhs) const {
if (lhs->priority != rhs->priority) {
diff --git a/media/libmediatranscoding/tests/Android.bp b/media/libmediatranscoding/tests/Android.bp
index 8191b00..904cf9b 100644
--- a/media/libmediatranscoding/tests/Android.bp
+++ b/media/libmediatranscoding/tests/Android.bp
@@ -37,6 +37,13 @@
srcs: ["TranscodingClientManager_tests.cpp"],
}
+cc_test {
+ name: "TranscodingJobScheduler_tests",
+ defaults: ["libmediatranscoding_test_defaults"],
+
+ srcs: ["TranscodingJobScheduler_tests.cpp"],
+}
+
//
// AdjustableMaxPriorityQueue unit test
//
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index d23ea87..b58c827 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -19,45 +19,52 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "TranscodingClientManagerTest"
-#include <aidl/android/media/BnTranscodingClientListener.h>
+#include <aidl/android/media/BnTranscodingClientCallback.h>
#include <aidl/android/media/IMediaTranscodingService.h>
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <gtest/gtest.h>
+#include <media/SchedulerClientInterface.h>
#include <media/TranscodingClientManager.h>
+#include <media/TranscodingRequest.h>
#include <utils/Log.h>
+#include <list>
+
namespace android {
using Status = ::ndk::ScopedAStatus;
-using aidl::android::media::BnTranscodingClientListener;
-using aidl::android::media::IMediaTranscodingService;
+using ::aidl::android::media::BnTranscodingClientCallback;
+using ::aidl::android::media::IMediaTranscodingService;
+using ::aidl::android::media::TranscodingErrorCode;
+using ::aidl::android::media::TranscodingJobParcel;
+using ::aidl::android::media::TranscodingRequestParcel;
+using ::aidl::android::media::TranscodingResultParcel;
-constexpr int32_t kInvalidClientPid = -1;
-constexpr int32_t kInvalidClientUid = -1;
+constexpr pid_t kInvalidClientPid = -1;
constexpr const char* kInvalidClientName = "";
constexpr const char* kInvalidClientPackage = "";
-constexpr int32_t kClientPid = 2;
-constexpr int32_t kClientUid = 3;
+constexpr pid_t kClientPid = 2;
+constexpr uid_t kClientUid = 3;
constexpr const char* kClientName = "TestClientName";
constexpr const char* kClientPackage = "TestClientPackage";
-struct TestClient : public BnTranscodingClientListener {
- TestClient() {
- ALOGD("TestClient Created");
- }
+struct TestClientCallback : public BnTranscodingClientCallback {
+ TestClientCallback() { ALOGI("TestClientCallback Created"); }
- Status onTranscodingFinished(
- int32_t /* in_jobId */,
- const ::aidl::android::media::TranscodingResultParcel& /* in_result */) override {
+ virtual ~TestClientCallback() { ALOGI("TestClientCallback destroyed"); };
+
+ Status onTranscodingFinished(int32_t in_jobId,
+ const TranscodingResultParcel& in_result) override {
+ EXPECT_EQ(in_jobId, in_result.jobId);
+ mEventQueue.push_back(Finished(in_jobId));
return Status::ok();
}
- Status onTranscodingFailed(
- int32_t /* in_jobId */,
- ::aidl::android::media::TranscodingErrorCode /*in_errorCode */) override {
+ Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode /*in_errorCode */) override {
+ mEventQueue.push_back(Failed(in_jobId));
return Status::ok();
}
@@ -70,142 +77,371 @@
return Status::ok();
}
- virtual ~TestClient() { ALOGI("TestClient destroyed"); };
+ struct Event {
+ enum {
+ NoEvent,
+ Finished,
+ Failed,
+ } type;
+ int32_t jobId;
+ };
+
+ static constexpr Event NoEvent = {Event::NoEvent, 0};
+#define DECLARE_EVENT(action) \
+ static Event action(int32_t jobId) { return {Event::action, jobId}; }
+
+ DECLARE_EVENT(Finished);
+ DECLARE_EVENT(Failed);
+
+ const Event& popEvent() {
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+ return mPoppedEvent;
+ }
private:
- TestClient(const TestClient&) = delete;
- TestClient& operator=(const TestClient&) = delete;
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+
+ TestClientCallback(const TestClientCallback&) = delete;
+ TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+bool operator==(const TestClientCallback::Event& lhs, const TestClientCallback::Event& rhs) {
+ return lhs.type == rhs.type && lhs.jobId == rhs.jobId;
+}
+
+struct TestScheduler : public SchedulerClientInterface {
+ TestScheduler() { ALOGI("TestScheduler Created"); }
+
+ virtual ~TestScheduler() { ALOGI("TestScheduler Destroyed"); }
+
+ bool submit(int64_t clientId, int32_t jobId, pid_t /*pid*/,
+ const TranscodingRequestParcel& request,
+ const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+ if (mJobs.count(jobKey) > 0) {
+ return false;
+ }
+
+ // This is the secret name we'll check, to test error propagation from
+ // the scheduler back to client.
+ if (request.fileName == "bad_file") {
+ return false;
+ }
+
+ mJobs[jobKey].request = request;
+ mJobs[jobKey].callback = clientCallback;
+
+ mLastJob = jobKey;
+ return true;
+ }
+
+ bool cancel(int64_t clientId, int32_t jobId) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+
+ if (mJobs.count(jobKey) == 0) {
+ return false;
+ }
+ mJobs.erase(jobKey);
+ return true;
+ }
+
+ bool getJob(int64_t clientId, int32_t jobId, TranscodingRequestParcel* request) override {
+ JobKeyType jobKey = std::make_pair(clientId, jobId);
+ if (mJobs.count(jobKey) == 0) {
+ return false;
+ }
+
+ *(TranscodingRequest*)request = mJobs[jobKey].request;
+ return true;
+ }
+
+ void finishLastJob() {
+ auto it = mJobs.find(mLastJob);
+ if (it == mJobs.end()) {
+ return;
+ }
+ {
+ auto clientCallback = it->second.callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFinished(
+ mLastJob.second, TranscodingResultParcel({mLastJob.second, 0}));
+ }
+ }
+ mJobs.erase(it);
+ }
+
+ void abortLastJob() {
+ auto it = mJobs.find(mLastJob);
+ if (it == mJobs.end()) {
+ return;
+ }
+ {
+ auto clientCallback = it->second.callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingFailed(
+ mLastJob.second, TranscodingErrorCode::kUnknown);
+ }
+ }
+ mJobs.erase(it);
+ }
+
+ struct Job {
+ TranscodingRequest request;
+ std::weak_ptr<ITranscodingClientCallback> callback;
+ };
+
+ typedef std::pair<int64_t, int32_t> JobKeyType;
+ std::map<JobKeyType, Job> mJobs;
+ JobKeyType mLastJob;
};
class TranscodingClientManagerTest : public ::testing::Test {
- public:
- TranscodingClientManagerTest() : mClientManager(TranscodingClientManager::getInstance()) {
+public:
+ TranscodingClientManagerTest()
+ : mScheduler(new TestScheduler()),
+ mClientManager(new TranscodingClientManager(mScheduler)) {
ALOGD("TranscodingClientManagerTest created");
}
void SetUp() override {
- mClientListener = ::ndk::SharedRefBase::make<TestClient>();
- mClientListener2 = ::ndk::SharedRefBase::make<TestClient>();
- mClientListener3 = ::ndk::SharedRefBase::make<TestClient>();
+ mClientCallback1 = ::ndk::SharedRefBase::make<TestClientCallback>();
+ mClientCallback2 = ::ndk::SharedRefBase::make<TestClientCallback>();
+ mClientCallback3 = ::ndk::SharedRefBase::make<TestClientCallback>();
}
- void TearDown() override {
- ALOGI("TranscodingClientManagerTest tear down");
- }
+ void TearDown() override { ALOGI("TranscodingClientManagerTest tear down"); }
~TranscodingClientManagerTest() { ALOGD("TranscodingClientManagerTest destroyed"); }
- TranscodingClientManager& mClientManager;
- std::shared_ptr<ITranscodingClientListener> mClientListener;
- std::shared_ptr<ITranscodingClientListener> mClientListener2;
- std::shared_ptr<ITranscodingClientListener> mClientListener3;
+ void addMultipleClients() {
+ EXPECT_EQ(mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient1),
+ OK);
+ EXPECT_NE(mClient1, nullptr);
+
+ EXPECT_EQ(mClientManager->addClient(mClientCallback2, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient2),
+ OK);
+ EXPECT_NE(mClient2, nullptr);
+
+ EXPECT_EQ(mClientManager->addClient(mClientCallback3, kClientPid, kClientUid, kClientName,
+ kClientPackage, &mClient3),
+ OK);
+ EXPECT_NE(mClient3, nullptr);
+
+ EXPECT_EQ(mClientManager->getNumOfClients(), 3);
+ }
+
+ void unregisterMultipleClients() {
+ EXPECT_TRUE(mClient1->unregister().isOk());
+ EXPECT_TRUE(mClient2->unregister().isOk());
+ EXPECT_TRUE(mClient3->unregister().isOk());
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
+ }
+
+ std::shared_ptr<TestScheduler> mScheduler;
+ std::shared_ptr<TranscodingClientManager> mClientManager;
+ std::shared_ptr<ITranscodingClient> mClient1;
+ std::shared_ptr<ITranscodingClient> mClient2;
+ std::shared_ptr<ITranscodingClient> mClient3;
+ std::shared_ptr<TestClientCallback> mClientCallback1;
+ std::shared_ptr<TestClientCallback> mClientCallback2;
+ std::shared_ptr<TestClientCallback> mClientCallback3;
};
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientListener) {
- // Add a client with null listener and expect failure.
+TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientCallback) {
+ // Add a client with null callback and expect failure.
std::shared_ptr<ITranscodingClient> client;
- status_t err = mClientManager.addClient(nullptr,
- kClientPid, kClientUid, kClientName, kClientPackage, &client);
+ status_t err = mClientManager->addClient(nullptr, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
EXPECT_EQ(err, BAD_VALUE);
}
TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPid) {
// Add a client with invalid Pid and expect failure.
std::shared_ptr<ITranscodingClient> client;
- status_t err = mClientManager.addClient(mClientListener,
- kInvalidClientPid, kClientUid, kClientName, kClientPackage, &client);
- EXPECT_EQ(err, BAD_VALUE);
-}
-
-TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientUid) {
- // Add a client with invalid Uid and expect failure.
- std::shared_ptr<ITranscodingClient> client;
- status_t err = mClientManager.addClient(mClientListener,
- kClientPid, kInvalidClientUid, kClientName, kClientPackage, &client);
+ status_t err = mClientManager->addClient(mClientCallback1, kInvalidClientPid, kClientUid,
+ kClientName, kClientPackage, &client);
EXPECT_EQ(err, BAD_VALUE);
}
TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientName) {
// Add a client with invalid name and expect failure.
std::shared_ptr<ITranscodingClient> client;
- status_t err = mClientManager.addClient(mClientListener,
- kClientPid, kClientUid, kInvalidClientName, kClientPackage, &client);
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid,
+ kInvalidClientName, kClientPackage, &client);
EXPECT_EQ(err, BAD_VALUE);
}
TEST_F(TranscodingClientManagerTest, TestAddingWithInvalidClientPackageName) {
// Add a client with invalid packagename and expect failure.
std::shared_ptr<ITranscodingClient> client;
- status_t err = mClientManager.addClient(mClientListener,
- kClientPid, kClientUid, kClientName, kInvalidClientPackage, &client);
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kInvalidClientPackage, &client);
EXPECT_EQ(err, BAD_VALUE);
}
TEST_F(TranscodingClientManagerTest, TestAddingValidClient) {
- // Add a valid client, should succeed
+ // Add a valid client, should succeed.
std::shared_ptr<ITranscodingClient> client;
- status_t err = mClientManager.addClient(mClientListener,
- kClientPid, kClientUid, kClientName, kClientPackage, &client);
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
EXPECT_EQ(err, OK);
EXPECT_NE(client.get(), nullptr);
- EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
- // unregister client, should succeed
+ // Unregister client, should succeed.
Status status = client->unregister();
EXPECT_TRUE(status.isOk());
- EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
}
TEST_F(TranscodingClientManagerTest, TestAddingDupliacteClient) {
std::shared_ptr<ITranscodingClient> client;
- status_t err = mClientManager.addClient(mClientListener,
- kClientPid, kClientUid, kClientName, kClientPackage, &client);
+ status_t err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, kClientName,
+ kClientPackage, &client);
EXPECT_EQ(err, OK);
EXPECT_NE(client.get(), nullptr);
- EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
std::shared_ptr<ITranscodingClient> dupClient;
- err = mClientManager.addClient(mClientListener,
- kClientPid, kClientUid, "dupClient", "dupPackage", &dupClient);
+ err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+ "dupPackage", &dupClient);
EXPECT_EQ(err, ALREADY_EXISTS);
EXPECT_EQ(dupClient.get(), nullptr);
- EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
Status status = client->unregister();
EXPECT_TRUE(status.isOk());
- EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
- err = mClientManager.addClient(mClientListener,
- kClientPid, kClientUid, "dupClient", "dupPackage", &dupClient);
+ err = mClientManager->addClient(mClientCallback1, kClientPid, kClientUid, "dupClient",
+ "dupPackage", &dupClient);
EXPECT_EQ(err, OK);
EXPECT_NE(dupClient.get(), nullptr);
- EXPECT_EQ(mClientManager.getNumOfClients(), 1);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 1);
status = dupClient->unregister();
EXPECT_TRUE(status.isOk());
- EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+ EXPECT_EQ(mClientManager->getNumOfClients(), 0);
}
TEST_F(TranscodingClientManagerTest, TestAddingMultipleClient) {
- std::shared_ptr<ITranscodingClient> client1, client2, client3;
+ addMultipleClients();
+ unregisterMultipleClients();
+}
- EXPECT_EQ(mClientManager.addClient(mClientListener,
- kClientPid, kClientUid, kClientName, kClientPackage, &client1), OK);
- EXPECT_NE(client1, nullptr);
+TEST_F(TranscodingClientManagerTest, TestSubmitCancelGetJobs) {
+ addMultipleClients();
- EXPECT_EQ(mClientManager.addClient(mClientListener2,
- kClientPid, kClientUid, kClientName, kClientPackage, &client2), OK);
- EXPECT_NE(client2, nullptr);
+ // Test jobId assignment.
+ TranscodingRequestParcel request;
+ request.fileName = "test_file_0";
+ TranscodingJobParcel job;
+ bool result;
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
- EXPECT_EQ(mClientManager.addClient(mClientListener3,
- kClientPid, kClientUid, kClientName, kClientPackage, &client3), OK);
- EXPECT_NE(client3, nullptr);
+ request.fileName = "test_file_1";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
- EXPECT_EQ(mClientManager.getNumOfClients(), 3);
- EXPECT_TRUE(client1->unregister().isOk());
- EXPECT_TRUE(client2->unregister().isOk());
- EXPECT_TRUE(client3->unregister().isOk());
- EXPECT_EQ(mClientManager.getNumOfClients(), 0);
+ request.fileName = "test_file_2";
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 2);
+
+ // Test submit bad request (no valid fileName) fails.
+ TranscodingRequestParcel badRequest;
+ badRequest.fileName = "bad_file";
+ EXPECT_TRUE(mClient1->submitRequest(badRequest, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get jobs by id.
+ EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+ EXPECT_EQ(job.jobId, 2);
+ EXPECT_EQ(job.request.fileName, "test_file_2");
+ EXPECT_TRUE(result);
+
+ // Test get jobs by invalid id fails.
+ EXPECT_TRUE(mClient1->getJobWithId(100, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel non-existent job fail.
+ EXPECT_TRUE(mClient2->cancelJob(100, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test cancel valid jobId in arbitrary order.
+ EXPECT_TRUE(mClient1->cancelJob(2, &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(0, &result).isOk());
+ EXPECT_TRUE(result);
+
+ EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+ EXPECT_TRUE(result);
+
+ // Test cancel job again fails.
+ EXPECT_TRUE(mClient1->cancelJob(1, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test get job after cancel fails.
+ EXPECT_TRUE(mClient1->getJobWithId(2, &job, &result).isOk());
+ EXPECT_FALSE(result);
+
+ // Test jobId independence for each client.
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
+
+ unregisterMultipleClients();
+}
+
+TEST_F(TranscodingClientManagerTest, TestClientCallback) {
+ addMultipleClients();
+
+ TranscodingRequestParcel request;
+ request.fileName = "test_file_name";
+ TranscodingJobParcel job;
+ bool result;
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ mScheduler->finishLastJob();
+ EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Finished(job.jobId));
+
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 1);
+
+ mScheduler->abortLastJob();
+ EXPECT_EQ(mClientCallback1->popEvent(), TestClientCallback::Failed(job.jobId));
+
+ EXPECT_TRUE(mClient1->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 2);
+
+ EXPECT_TRUE(mClient2->submitRequest(request, &job, &result).isOk());
+ EXPECT_TRUE(result);
+ EXPECT_EQ(job.jobId, 0);
+
+ mScheduler->finishLastJob();
+ EXPECT_EQ(mClientCallback2->popEvent(), TestClientCallback::Finished(job.jobId));
+
+ unregisterMultipleClients();
}
} // namespace android
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
new file mode 100644
index 0000000..d062d93
--- /dev/null
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * 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.
+ */
+
+// Unit Test for TranscodingJobScheduler
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingJobSchedulerTest"
+
+#include <aidl/android/media/BnTranscodingClientCallback.h>
+#include <aidl/android/media/IMediaTranscodingService.h>
+#include <aidl/android/media/ITranscodingClient.h>
+#include <aidl/android/media/ITranscodingClientCallback.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+#include <media/TranscodingClientManager.h>
+#include <media/TranscodingJobScheduler.h>
+#include <utils/Log.h>
+
+namespace android {
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::media::BnTranscodingClientCallback;
+using aidl::android::media::IMediaTranscodingService;
+using aidl::android::media::ITranscodingClient;
+
+constexpr int64_t kClientId = 1000;
+constexpr int32_t kClientJobId = 0;
+constexpr pid_t kClientPid = 5000;
+constexpr pid_t kInvalidPid = -1;
+
+#define CLIENT(n) (kClientId + (n))
+#define JOB(n) (kClientJobId + (n))
+#define PID(n) (kClientPid + (n))
+
+class TestCallback : public TranscoderInterface, public ProcessInfoInterface {
+public:
+ TestCallback() : mTopPid(-1), mLastError(TranscodingErrorCode::kUnknown) {}
+ virtual ~TestCallback() {}
+
+ // TranscoderInterface
+ void start(int64_t clientId, int32_t jobId) override {
+ mEventQueue.push_back(Start(clientId, jobId));
+ }
+ void pause(int64_t clientId, int32_t jobId) override {
+ mEventQueue.push_back(Pause(clientId, jobId));
+ }
+ void resume(int64_t clientId, int32_t jobId) override {
+ mEventQueue.push_back(Resume(clientId, jobId));
+ }
+
+ // ProcessInfoInterface
+ bool isProcessOnTop(pid_t pid) override { return pid == mTopPid; }
+
+ void onFinished(int64_t clientId, int32_t jobId) {
+ mEventQueue.push_back(Finished(clientId, jobId));
+ }
+
+ void onFailed(int64_t clientId, int32_t jobId, TranscodingErrorCode err) {
+ mLastError = err;
+ mEventQueue.push_back(Failed(clientId, jobId));
+ }
+
+ void setTop(pid_t pid) { mTopPid = pid; }
+
+ TranscodingErrorCode getLastError() {
+ TranscodingErrorCode result = mLastError;
+ mLastError = TranscodingErrorCode::kUnknown;
+ return result;
+ }
+
+ struct Event {
+ enum { NoEvent, Start, Pause, Resume, Finished, Failed } type;
+ int64_t clientId;
+ int32_t jobId;
+ };
+
+ static constexpr Event NoEvent = {Event::NoEvent, 0, 0};
+
+#define DECLARE_EVENT(action) \
+ static Event action(int64_t clientId, int32_t jobId) { \
+ return {Event::action, clientId, jobId}; \
+ }
+
+ DECLARE_EVENT(Start);
+ DECLARE_EVENT(Pause);
+ DECLARE_EVENT(Resume);
+ DECLARE_EVENT(Finished);
+ DECLARE_EVENT(Failed);
+
+ const Event& popEvent() {
+ if (mEventQueue.empty()) {
+ mPoppedEvent = NoEvent;
+ } else {
+ mPoppedEvent = *mEventQueue.begin();
+ mEventQueue.pop_front();
+ }
+ return mPoppedEvent;
+ }
+
+private:
+ Event mPoppedEvent;
+ std::list<Event> mEventQueue;
+ pid_t mTopPid;
+ TranscodingErrorCode mLastError;
+};
+
+bool operator==(const TestCallback::Event& lhs, const TestCallback::Event& rhs) {
+ return lhs.type == rhs.type && lhs.clientId == rhs.clientId && lhs.jobId == rhs.jobId;
+}
+
+struct TestClientCallback : public BnTranscodingClientCallback {
+ TestClientCallback(TestCallback* owner, int64_t clientId) : mOwner(owner), mClientId(clientId) {
+ ALOGD("TestClient Created");
+ }
+
+ Status onTranscodingFinished(int32_t in_jobId,
+ const TranscodingResultParcel& in_result) override {
+ EXPECT_EQ(in_jobId, in_result.jobId);
+ mOwner->onFinished(mClientId, in_jobId);
+ return Status::ok();
+ }
+
+ Status onTranscodingFailed(int32_t in_jobId, TranscodingErrorCode in_errorCode) override {
+ mOwner->onFailed(mClientId, in_jobId, in_errorCode);
+ return Status::ok();
+ }
+
+ Status onAwaitNumberOfJobsChanged(int32_t /* in_jobId */, int32_t /* in_oldAwaitNumber */,
+ int32_t /* in_newAwaitNumber */) override {
+ return Status::ok();
+ }
+
+ Status onProgressUpdate(int32_t /* in_jobId */, int32_t /* in_progress */) override {
+ return Status::ok();
+ }
+
+ virtual ~TestClientCallback() { ALOGI("TestClient destroyed"); };
+
+private:
+ TestCallback* mOwner;
+ int64_t mClientId;
+ TestClientCallback(const TestClientCallback&) = delete;
+ TestClientCallback& operator=(const TestClientCallback&) = delete;
+};
+
+class TranscodingJobSchedulerTest : public ::testing::Test {
+public:
+ TranscodingJobSchedulerTest() { ALOGI("TranscodingJobSchedulerTest created"); }
+
+ void SetUp() override {
+ ALOGI("TranscodingJobSchedulerTest set up");
+ mCallback.reset(new TestCallback());
+ mScheduler.reset(new TranscodingJobScheduler(mCallback, mCallback));
+
+ // Set priority only, ignore other fields for now.
+ mOfflineRequest.priority = TranscodingJobPriority::kUnspecified;
+ mRealtimeRequest.priority = TranscodingJobPriority::kHigh;
+ mClientCallback0 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(0));
+ mClientCallback1 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(1));
+ mClientCallback2 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(2));
+ mClientCallback3 =
+ ::ndk::SharedRefBase::make<TestClientCallback>(mCallback.get(), CLIENT(3));
+ }
+
+ void TearDown() override { ALOGI("TranscodingJobSchedulerTest tear down"); }
+
+ ~TranscodingJobSchedulerTest() { ALOGD("TranscodingJobSchedulerTest destroyed"); }
+
+ std::shared_ptr<TestCallback> mCallback;
+ std::shared_ptr<TranscodingJobScheduler> mScheduler;
+ TranscodingRequestParcel mOfflineRequest;
+ TranscodingRequestParcel mRealtimeRequest;
+ std::shared_ptr<TestClientCallback> mClientCallback0;
+ std::shared_ptr<TestClientCallback> mClientCallback1;
+ std::shared_ptr<TestClientCallback> mClientCallback2;
+ std::shared_ptr<TestClientCallback> mClientCallback3;
+};
+
+TEST_F(TranscodingJobSchedulerTest, TestSubmitJob) {
+ ALOGD("TestSubmitJob");
+
+ // Start with PID(1) on top.
+ mCallback->setTop(PID(1));
+
+ // Submit offline job to CLIENT(0) in PID(0).
+ // Should start immediately (because this is the only job).
+ mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), 0));
+
+ // Submit real-time job to CLIENT(0).
+ // Should pause offline job and start new job, even if PID(0) is not on top.
+ mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job to CLIENT(0), should be queued after the previous job.
+ mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit real-time job to CLIENT(1) in same pid, should be queued after the previous job.
+ mScheduler->submit(CLIENT(1), JOB(0), PID(0), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit real-time job to CLIENT(2) in PID(1).
+ // Should pause previous job and start new job, because PID(1) is top.
+ mCallback->setTop(PID(1));
+ mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+
+ // Submit offline job, shouldn't generate any event.
+ mScheduler->submit(CLIENT(2), JOB(1), PID(1), mOfflineRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ mCallback->setTop(PID(0));
+ // Submit real-time job to CLIENT(1) in PID(0).
+ // Should pause current job, and resume last job in PID(0).
+ mScheduler->submit(CLIENT(1), JOB(1), PID(0), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(1)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestCancelJob) {
+ ALOGD("TestCancelJob");
+
+ // Submit real-time job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should not start.
+ mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit offline job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), PID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Cancel queued real-time job.
+ // Cancel real-time job JOB(1), should be cancelled.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(1)));
+
+ // Cancel queued offline job.
+ // Cancel offline job JOB(2), should be cancelled.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(2)));
+
+ // Submit offline job JOB(3), shouldn't cause any event.
+ mScheduler->submit(CLIENT(0), JOB(3), PID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Cancel running real-time job JOB(0).
+ // - Should be paused first then cancelled.
+ // - Should also start offline job JOB(2) because real-time queue is empty.
+ EXPECT_TRUE(mScheduler->cancel(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(3)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFinishJob) {
+ ALOGD("TestFinishJob");
+
+ // Fail without any jobs submitted, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit offline job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should pause offline job and start immediately.
+ mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Fail when the job never started, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(2));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // PID(1) moves to top.
+ mCallback->setTop(PID(1));
+ // Submit real-time job to CLIENT(1) in PID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), PID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+
+ // Simulate Fail that arrived late, after pause issued by scheduler.
+ // Should still be propagated to client, but shouldn't trigger any new start.
+ mScheduler->onFinish(CLIENT(0), JOB(1));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(1)));
+
+ // Fail running real-time job, should start next real-time job in queue.
+ mScheduler->onFinish(CLIENT(1), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+
+ // Fail running real-time job, should resume next job (offline job) in queue.
+ mScheduler->onFinish(CLIENT(0), JOB(2));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Fail running offline job.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
+
+ // Duplicate fail for last job, should be ignored.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestFailJob) {
+ ALOGD("TestFailJob");
+
+ // Fail without any jobs submitted, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Submit offline job JOB(0), should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), PID(0), mOfflineRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit real-time job JOB(1), should pause offline job and start immediately.
+ mScheduler->submit(CLIENT(0), JOB(1), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(1)));
+
+ // Submit real-time job JOB(2), should not start.
+ mScheduler->submit(CLIENT(0), JOB(2), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Fail when the job never started, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // PID(1) moves to top.
+ mCallback->setTop(PID(1));
+ // Submit real-time job to CLIENT(1) in PID(1), should pause previous job and start new job.
+ mScheduler->submit(CLIENT(1), JOB(0), PID(1), mRealtimeRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(1)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+
+ // Simulate Fail that arrived late, after pause issued by scheduler.
+ // Should still be propagated to client, but shouldn't trigger any new start.
+ mScheduler->onError(CLIENT(0), JOB(1), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(1)));
+
+ // Fail running real-time job, should start next real-time job in queue.
+ mScheduler->onError(CLIENT(1), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(1), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(2)));
+
+ // Fail running real-time job, should resume next job (offline job) in queue.
+ mScheduler->onError(CLIENT(0), JOB(2), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(2)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Fail running offline job, and test error code propagation.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kInvalidBitstream);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Failed(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->getLastError(), TranscodingErrorCode::kInvalidBitstream);
+
+ // Duplicate fail for last job, should be ignored.
+ mScheduler->onError(CLIENT(0), JOB(0), TranscodingErrorCode::kUnknown);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestTopProcessChanged) {
+ ALOGD("TestTopProcessChanged");
+
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), PID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move PID(1) to top.
+ mCallback->setTop(PID(1));
+ // Submit real-time job to CLIENT(2) in different pid PID(1).
+ // Should pause previous job and start new job.
+ mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+
+ // Bring PID(0) back to top.
+ mCallback->setTop(PID(0));
+ mScheduler->onTopProcessChanged(PID(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Bring invalid process to top.
+ mScheduler->onTopProcessChanged(kInvalidPid);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Finish job, next real-time job should resume.
+ mScheduler->onFinish(CLIENT(0), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+
+ // Finish job, offline job should start.
+ mScheduler->onFinish(CLIENT(2), JOB(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Finished(CLIENT(2), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(1), JOB(0)));
+}
+
+TEST_F(TranscodingJobSchedulerTest, TestResourceLost) {
+ ALOGD("TestResourceLost");
+
+ // Submit real-time job to CLIENT(0), job should start immediately.
+ mScheduler->submit(CLIENT(0), JOB(0), PID(0), mRealtimeRequest, mClientCallback0);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(0), JOB(0)));
+
+ // Submit offline job to CLIENT(0), should not start.
+ mScheduler->submit(CLIENT(1), JOB(0), PID(0), mOfflineRequest, mClientCallback1);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move PID(1) to top.
+ mCallback->setTop(PID(1));
+
+ // Submit real-time job to CLIENT(2) in different pid PID(1).
+ // Should pause previous job and start new job.
+ mScheduler->submit(CLIENT(2), JOB(0), PID(1), mRealtimeRequest, mClientCallback2);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Pause(CLIENT(0), JOB(0)));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(2), JOB(0)));
+
+ // Test 1: No queue change during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Signal resource available, CLIENT(2) should resume.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(2), JOB(0)));
+
+ // Test 2: Change of queue order during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move PID(0) back to top, should have no resume due to no resource.
+ mScheduler->onTopProcessChanged(PID(0));
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Signal resource available, CLIENT(0) should resume.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Resume(CLIENT(0), JOB(0)));
+
+ // Test 3: Adding new queue during resource loss.
+ // Signal resource lost.
+ mScheduler->onResourceLost();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Move PID(2) to top.
+ mCallback->setTop(PID(2));
+
+ // Submit real-time job to CLIENT(3) in PID(2), job shouldn't start due to no resource.
+ mScheduler->submit(CLIENT(3), JOB(0), PID(2), mRealtimeRequest, mClientCallback3);
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::NoEvent);
+
+ // Signal resource available, CLIENT(3)'s job should start.
+ mScheduler->onResourceAvailable();
+ EXPECT_EQ(mCallback->popEvent(), TestCallback::Start(CLIENT(3), JOB(0)));
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
index ec15a21..7ab92ae 100644
--- a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -27,3 +27,7 @@
#adb shell /data/nativetest64/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
adb shell /data/nativetest/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorityQueue_tests
+echo "testing TranscodingJobScheduler"
+#adb shell /data/nativetest64/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
+adb shell /data/nativetest/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
+