blob: 9d745c4236eba7520b0ad51654288935c2acd40b [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fake_codec_adapter.h>
#include <fuchsia/media/cpp/fidl.h>
#include <fuchsia/media/drm/cpp/fidl.h>
#include <fuchsia/mediacodec/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fit/function.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/zx/time.h>
#include <gtest/gtest.h>
#include "lib/media/codec_impl/codec_impl.h"
namespace {
auto CreateDecoderParams() {
fuchsia::mediacodec::CreateDecoder_Params params;
params.mutable_input_details()->set_format_details_version_ordinal(0).set_mime_type("video/vp9");
return params;
}
auto CreateEncoderParams() {
fuchsia::mediacodec::CreateEncoder_Params params;
params.mutable_input_details()->set_format_details_version_ordinal(0).set_mime_type("audio/sbc");
return params;
}
auto CreateDecryptorParams() {
fuchsia::media::drm::DecryptorParams params;
params.mutable_input_details()->set_format_details_version_ordinal(0);
return params;
}
} // namespace
class CodecImplLifetime : public gtest::RealLoopFixture {
protected:
CodecImplLifetime()
: loop_separate_thread_(&kAsyncLoopConfigNoAttachToCurrentThread),
admission_control_(dispatcher()) {
// nothing else to do here
}
~CodecImplLifetime() {
// Force to teardown before admission_control_.
RunLoopUntilIdle();
QuitLoop();
}
void SetUp() override { loop_separate_thread_.StartThread("separate_thread"); }
void TearDown() override {
// Force any failure during ~CodecImpl to have more obvious stack.
codec_impl_ = nullptr;
}
void Create(bool bind = true, bool delete_async = false,
CodecImpl::StreamProcessorParams params = CreateDecoderParams()) {
// Just hold onto the server end and never connect it to anything, for now.
sysmem_request_ = sysmem_client_.NewRequest();
codec_request_ = codec_client_handle_.NewRequest();
ZX_DEBUG_ASSERT(!delete_async || bind);
std::unique_ptr<CodecImpl> codec_impl;
admission_control_.TryAddCodec(
true, [this, bind, delete_async, params = std::move(params),
&codec_impl](std::unique_ptr<CodecAdmission> codec_admission) mutable {
codec_impl = std::make_unique<CodecImpl>(
std::move(sysmem_client_), std::move(codec_admission), dispatcher(), thrd_current(),
std::move(params), std::move(codec_request_));
auto fake_codec_adapter =
std::make_unique<FakeCodecAdapter>(codec_impl->lock(), codec_impl.get());
fake_codec_adapter_ = fake_codec_adapter.get();
codec_impl->SetCoreCodecAdapter(std::move(fake_codec_adapter));
if (!bind) {
return;
}
codec_impl->BindAsync([this, delete_async] {
if (!delete_async) {
codec_impl_ = nullptr;
} else {
zx_status_t status = async::PostTask(dispatcher(), [this] { codec_impl_ = nullptr; });
ZX_DEBUG_ASSERT(status == ZX_OK);
}
error_handler_ran_ = true;
});
});
RunLoopUntil([&codec_impl] { return !!codec_impl; });
ZX_DEBUG_ASSERT(codec_impl);
codec_impl_ = std::move(codec_impl);
}
void StartSyncChain() {
if (codec_client_ptr_) {
codec_client_ptr_->Sync([this] {
++sync_completion_count_;
StartSyncChain();
});
}
}
void PostToSeparateThread(fit::closure to_run) {
zx_status_t status = async::PostTask(loop_separate_thread_.dispatcher(), std::move(to_run));
ZX_DEBUG_ASSERT(status == ZX_OK);
}
async::Loop loop_separate_thread_;
CodecAdmissionControl admission_control_;
// The server end isn't connected to anything, for now.
fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem_client_;
fidl::InterfaceRequest<fuchsia::sysmem::Allocator> sysmem_request_;
// Tests that don't intend to process any received messages use
// InterfaceHandle while those that do intend to process received messages
// use InterfacePtr.
fidl::InterfaceHandle<fuchsia::media::StreamProcessor> codec_client_handle_;
fuchsia::media::StreamProcessorPtr codec_client_ptr_;
fidl::InterfaceRequest<fuchsia::media::StreamProcessor> codec_request_;
std::unique_ptr<CodecImpl> codec_impl_;
FakeCodecAdapter* fake_codec_adapter_;
bool error_handler_ran_ = false;
uint64_t sync_completion_count_ = 0;
};
TEST_F(CodecImplLifetime, CreateDelete) {
Create(false);
codec_impl_ = nullptr;
RunLoopUntilIdle();
EXPECT_FALSE(error_handler_ran_);
}
TEST_F(CodecImplLifetime, CreateBindDelete) {
Create();
codec_impl_ = nullptr;
RunLoopUntilIdle();
EXPECT_FALSE(error_handler_ran_);
}
TEST_F(CodecImplLifetime, CreateBindChannelClose) {
Create();
// Close the client end of the StreamProcessor channel.
codec_client_handle_.TakeChannel().reset();
RunLoopUntil([this] { return error_handler_ran_; });
EXPECT_TRUE(error_handler_ran_);
}
TEST_F(CodecImplLifetime, CreateBindChannelCloseDeleteAsync) {
Create(true, true);
// Close the client end of the StreamProcessor channel.
codec_client_handle_.TakeChannel().reset();
RunLoopUntil([this] { return error_handler_ran_; });
// This doesn't imply that !codec_impl_ yet, because deletion
// is happening async.
EXPECT_TRUE(error_handler_ran_);
RunLoopUntil([this] { return !codec_impl_; });
EXPECT_TRUE(!codec_impl_);
}
TEST_F(CodecImplLifetime, CreateBindChannelCloseDeleteAsyncWithOngoingSyncs) {
// More than one thread is involved, so do this several times in case it helps catch something
// bad that doesn't always happen.
constexpr uint64_t kIterCount = 20;
for (uint64_t iter = 0; iter < kIterCount; ++iter) {
Create(true, true);
codec_client_ptr_.Bind(codec_client_handle_.TakeChannel());
constexpr uint32_t kInFlightSyncTarget = 5;
for (uint32_t i = 0; i < kInFlightSyncTarget; ++i) {
// Each started chain kicks another Sync() on each completion, any time
// RunLoopUntil() is running.
StartSyncChain();
}
// Make sure the sync chains will re-trigger new syncs while RunLoopUntil() is
// running.
RunLoopUntil([this] { return sync_completion_count_ >= kInFlightSyncTarget * 2; });
// Trigger an error as if the FakeCodecAdapter had triggered it, with a slight
// delay before the trigger so we get some coverage of syncs happening
// continuously while the failure handling happens.
PostToSeparateThread([this] {
zx::nanosleep(zx::deadline_after(zx::msec(20)));
static_cast<CodecAdapterEvents*>(codec_impl_.get())
->onCoreCodecFailCodec(
"CreateBindChannelCloseDeleteAsyncWithOngoingSyncs triggering failure");
});
RunLoopUntil([this] { return !codec_impl_; });
EXPECT_TRUE(!codec_impl_);
}
}
TEST_F(CodecImplLifetime, CreateBindDeleteEncoder) {
Create(true, false, CreateEncoderParams());
codec_impl_ = nullptr;
RunLoopUntilIdle();
EXPECT_FALSE(error_handler_ran_);
}
TEST_F(CodecImplLifetime, CreateBindDeleteDecryptor) {
Create(true, false, CreateDecryptorParams());
codec_impl_ = nullptr;
RunLoopUntilIdle();
EXPECT_FALSE(error_handler_ran_);
}