| // Copyright 2019 The Chromium 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 "third_party/blink/renderer/modules/media_capabilities/media_capabilities.h" |
| |
| #include <math.h> |
| |
| #include <algorithm> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task/post_task.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_codecs.h" |
| #include "media/learning/common/media_learning_tasks.h" |
| #include "media/learning/common/target_histogram.h" |
| #include "media/learning/mojo/public/mojom/learning_task_controller.mojom-blink.h" |
| #include "media/mojo/mojom/media_metrics_provider.mojom-blink.h" |
| #include "media/mojo/mojom/media_types.mojom-blink.h" |
| #include "media/mojo/mojom/video_decode_perf_history.mojom-blink.h" |
| #include "media/mojo/mojom/watch_time_recorder.mojom-blink.h" |
| #include "media/video/mock_gpu_video_accelerator_factories.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/platform/web_size.h" |
| #include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_configuration.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_media_capabilities_info.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_media_configuration.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_media_decoding_configuration.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_video_configuration.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/navigator.h" |
| #include "third_party/blink/renderer/core/testing/page_test_base.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/testing/testing_platform_support.h" |
| #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_view.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h" |
| #include "third_party/googletest/src/googlemock/include/gmock/gmock-actions.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| using ::media::learning::FeatureValue; |
| using ::media::learning::ObservationCompletion; |
| using ::media::learning::TargetValue; |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::Return; |
| using ::testing::Unused; |
| |
| namespace blink { |
| |
| namespace { |
| |
| // Simulating the browser-side service. |
| class MockPerfHistoryService |
| : public media::mojom::blink::VideoDecodePerfHistory { |
| public: |
| void BindRequest(mojo::ScopedMessagePipeHandle handle) { |
| receiver_.Bind( |
| mojo::PendingReceiver<media::mojom::blink::VideoDecodePerfHistory>( |
| std::move(handle))); |
| receiver_.set_disconnect_handler(base::BindOnce( |
| &MockPerfHistoryService::OnConnectionError, base::Unretained(this))); |
| } |
| |
| void OnConnectionError() { receiver_.reset(); } |
| |
| // media::mojom::blink::VideoDecodePerfHistory implementation: |
| MOCK_METHOD2(GetPerfInfo, |
| void(media::mojom::blink::PredictionFeaturesPtr features, |
| GetPerfInfoCallback got_info_cb)); |
| |
| private: |
| mojo::Receiver<media::mojom::blink::VideoDecodePerfHistory> receiver_{this}; |
| }; |
| |
| class MockLearningTaskControllerService |
| : public media::learning::mojom::blink::LearningTaskController { |
| public: |
| void BindRequest(mojo::PendingReceiver< |
| media::learning::mojom::blink::LearningTaskController> |
| pending_receiver) { |
| receiver_.Bind(std::move(pending_receiver)); |
| receiver_.set_disconnect_handler( |
| base::BindOnce(&MockLearningTaskControllerService::OnConnectionError, |
| base::Unretained(this))); |
| } |
| |
| void OnConnectionError() { receiver_.reset(); } |
| |
| bool is_bound() const { return receiver_.is_bound(); } |
| |
| // media::mojom::blink::LearningTaskController implementation: |
| MOCK_METHOD3(BeginObservation, |
| void(const base::UnguessableToken& id, |
| const WTF::Vector<FeatureValue>& features, |
| const base::Optional<TargetValue>& default_target)); |
| MOCK_METHOD2(CompleteObservation, |
| void(const base::UnguessableToken& id, |
| const ObservationCompletion& completion)); |
| MOCK_METHOD1(CancelObservation, void(const base::UnguessableToken& id)); |
| MOCK_METHOD2(UpdateDefaultTarget, |
| void(const base::UnguessableToken& id, |
| const base::Optional<TargetValue>& default_target)); |
| MOCK_METHOD2(PredictDistribution, |
| void(const WTF::Vector<FeatureValue>& features, |
| PredictDistributionCallback callback)); |
| |
| private: |
| mojo::Receiver<media::learning::mojom::blink::LearningTaskController> |
| receiver_{this}; |
| }; |
| |
| class FakeMediaMetricsProvider |
| : public media::mojom::blink::MediaMetricsProvider { |
| public: |
| // Raw pointers to services owned by the test. |
| FakeMediaMetricsProvider( |
| MockLearningTaskControllerService* bad_window_service, |
| MockLearningTaskControllerService* nnr_service) |
| : bad_window_service_(bad_window_service), nnr_service_(nnr_service) {} |
| |
| ~FakeMediaMetricsProvider() override = default; |
| |
| void BindRequest(mojo::ScopedMessagePipeHandle handle) { |
| receiver_.Bind( |
| mojo::PendingReceiver<media::mojom::blink::MediaMetricsProvider>( |
| std::move(handle))); |
| receiver_.set_disconnect_handler(base::BindOnce( |
| &FakeMediaMetricsProvider::OnConnectionError, base::Unretained(this))); |
| } |
| |
| void OnConnectionError() { receiver_.reset(); } |
| |
| // mojom::WatchTimeRecorderProvider implementation: |
| void AcquireWatchTimeRecorder( |
| media::mojom::blink::PlaybackPropertiesPtr properties, |
| mojo::PendingReceiver<media::mojom::blink::WatchTimeRecorder> receiver) |
| override { |
| FAIL(); |
| } |
| void AcquireVideoDecodeStatsRecorder( |
| mojo::PendingReceiver<media::mojom::blink::VideoDecodeStatsRecorder> |
| receiver) override { |
| FAIL(); |
| } |
| void AcquireLearningTaskController( |
| const WTF::String& taskName, |
| mojo::PendingReceiver< |
| media::learning::mojom::blink::LearningTaskController> |
| pending_receiver) override { |
| if (taskName == media::learning::tasknames::kConsecutiveBadWindows) { |
| bad_window_service_->BindRequest(std::move(pending_receiver)); |
| return; |
| } |
| |
| if (taskName == media::learning::tasknames::kConsecutiveNNRs) { |
| nnr_service_->BindRequest(std::move(pending_receiver)); |
| return; |
| } |
| FAIL(); |
| } |
| void AcquirePlaybackEventsRecorder( |
| mojo::PendingReceiver<media::mojom::blink::PlaybackEventsRecorder> |
| receiver) override { |
| FAIL(); |
| } |
| void Initialize(bool is_mse, |
| media::mojom::MediaURLScheme url_scheme, |
| media::mojom::MediaStreamType media_stream_type) override {} |
| void OnError(media::mojom::PipelineStatus status) override {} |
| void SetIsEME() override {} |
| void SetTimeToMetadata(base::TimeDelta elapsed) override {} |
| void SetTimeToFirstFrame(base::TimeDelta elapsed) override {} |
| void SetTimeToPlayReady(base::TimeDelta elapsed) override {} |
| void SetContainerName( |
| media::mojom::blink::MediaContainerName container_name) override {} |
| void SetHasPlayed() override {} |
| void SetHaveEnough() override {} |
| void SetHasAudio(media::mojom::AudioCodec audio_codec) override {} |
| void SetHasVideo(media::mojom::VideoCodec video_codec) override {} |
| void SetVideoPipelineInfo( |
| media::mojom::blink::VideoDecoderInfoPtr info) override {} |
| void SetAudioPipelineInfo( |
| media::mojom::blink::AudioDecoderInfoPtr info) override {} |
| |
| private: |
| mojo::Receiver<media::mojom::blink::MediaMetricsProvider> receiver_{this}; |
| MockLearningTaskControllerService* bad_window_service_; |
| MockLearningTaskControllerService* nnr_service_; |
| }; |
| |
| // Simple helper for saving back-end callbacks for pending decodingInfo() calls. |
| // Callers can then manually fire the callbacks, gaining fine-grain control of |
| // the timing and order of their arrival. |
| class CallbackSaver { |
| public: |
| void SavePerfHistoryCallback( |
| media::mojom::blink::PredictionFeaturesPtr features, |
| MockPerfHistoryService::GetPerfInfoCallback got_info_cb) { |
| perf_history_cb_ = std::move(got_info_cb); |
| } |
| |
| void SaveBadWindowCallback( |
| Vector<media::learning::FeatureValue> features, |
| MockLearningTaskControllerService::PredictDistributionCallback |
| predict_cb) { |
| bad_window_cb_ = std::move(predict_cb); |
| } |
| |
| void SaveNnrCallback( |
| Vector<media::learning::FeatureValue> features, |
| MockLearningTaskControllerService::PredictDistributionCallback |
| predict_cb) { |
| nnr_cb_ = std::move(predict_cb); |
| } |
| |
| void SaveGpuFactoriesNotifyCallback(base::OnceClosure cb) { |
| gpu_factories_notify_cb_ = std::move(cb); |
| } |
| |
| MockPerfHistoryService::GetPerfInfoCallback& perf_history_cb() { |
| return perf_history_cb_; |
| } |
| |
| MockLearningTaskControllerService::PredictDistributionCallback& |
| bad_window_cb() { |
| return bad_window_cb_; |
| } |
| |
| MockLearningTaskControllerService::PredictDistributionCallback& nnr_cb() { |
| return nnr_cb_; |
| } |
| |
| base::OnceClosure& gpu_factories_notify_cb() { |
| return gpu_factories_notify_cb_; |
| } |
| |
| private: |
| MockPerfHistoryService::GetPerfInfoCallback perf_history_cb_; |
| MockLearningTaskControllerService::PredictDistributionCallback bad_window_cb_; |
| MockLearningTaskControllerService::PredictDistributionCallback nnr_cb_; |
| base::OnceClosure gpu_factories_notify_cb_; |
| }; |
| |
| class MockPlatform : public TestingPlatformSupport { |
| public: |
| MockPlatform() = default; |
| ~MockPlatform() override = default; |
| |
| MOCK_METHOD0(GetGpuFactories, media::GpuVideoAcceleratorFactories*()); |
| }; |
| |
| // This would typically be a test fixture, but we need it to be |
| // STACK_ALLOCATED() in order to use V8TestingScope, and we can't force that on |
| // whatever gtest class instantiates the fixture. |
| class MediaCapabilitiesTestContext { |
| STACK_ALLOCATED(); |
| |
| public: |
| MediaCapabilitiesTestContext() { |
| perf_history_service_ = std::make_unique<MockPerfHistoryService>(); |
| bad_window_service_ = std::make_unique<MockLearningTaskControllerService>(); |
| nnr_service_ = std::make_unique<MockLearningTaskControllerService>(); |
| fake_metrics_provider_ = std::make_unique<FakeMediaMetricsProvider>( |
| bad_window_service_.get(), nnr_service_.get()); |
| |
| CHECK(v8_scope_.GetExecutionContext() |
| ->GetBrowserInterfaceBroker() |
| .SetBinderForTesting( |
| media::mojom::blink::MediaMetricsProvider::Name_, |
| base::BindRepeating( |
| &FakeMediaMetricsProvider::BindRequest, |
| base::Unretained(fake_metrics_provider_.get())))); |
| |
| CHECK(v8_scope_.GetExecutionContext() |
| ->GetBrowserInterfaceBroker() |
| .SetBinderForTesting( |
| media::mojom::blink::VideoDecodePerfHistory::Name_, |
| base::BindRepeating( |
| &MockPerfHistoryService::BindRequest, |
| base::Unretained(perf_history_service_.get())))); |
| |
| media_capabilities_ = MediaCapabilities::mediaCapabilities( |
| *v8_scope_.GetWindow().navigator()); |
| } |
| |
| ~MediaCapabilitiesTestContext() { |
| CHECK(v8_scope_.GetExecutionContext() |
| ->GetBrowserInterfaceBroker() |
| .SetBinderForTesting( |
| media::mojom::blink::MediaMetricsProvider::Name_, {})); |
| |
| CHECK(v8_scope_.GetExecutionContext() |
| ->GetBrowserInterfaceBroker() |
| .SetBinderForTesting( |
| media::mojom::blink::VideoDecodePerfHistory::Name_, {})); |
| } |
| |
| ExceptionState& GetExceptionState() { return v8_scope_.GetExceptionState(); } |
| |
| ScriptState* GetScriptState() const { return v8_scope_.GetScriptState(); } |
| |
| v8::Isolate* GetIsolate() const { return GetScriptState()->GetIsolate(); } |
| |
| MediaCapabilities* GetMediaCapabilities() const { |
| return media_capabilities_.Get(); |
| } |
| |
| MockPerfHistoryService* GetPerfHistoryService() const { |
| return perf_history_service_.get(); |
| } |
| |
| MockLearningTaskControllerService* GetBadWindowService() const { |
| return bad_window_service_.get(); |
| } |
| |
| MockLearningTaskControllerService* GetNnrService() const { |
| return nnr_service_.get(); |
| } |
| |
| MockPlatform& GetMockPlatform() { return *mock_platform_; } |
| |
| void VerifyAndClearMockExpectations() { |
| testing::Mock::VerifyAndClearExpectations(GetPerfHistoryService()); |
| testing::Mock::VerifyAndClearExpectations(GetNnrService()); |
| testing::Mock::VerifyAndClearExpectations(GetBadWindowService()); |
| testing::Mock::VerifyAndClearExpectations(&GetMockPlatform()); |
| } |
| |
| private: |
| V8TestingScope v8_scope_; |
| ScopedTestingPlatformSupport<MockPlatform> mock_platform_; |
| std::unique_ptr<MockPerfHistoryService> perf_history_service_; |
| std::unique_ptr<FakeMediaMetricsProvider> fake_metrics_provider_; |
| Persistent<MediaCapabilities> media_capabilities_; |
| std::unique_ptr<MockLearningTaskControllerService> bad_window_service_; |
| std::unique_ptr<MockLearningTaskControllerService> nnr_service_; |
| }; |
| |
| // |kContentType|, |kCodec|, and |kCodecProfile| must match. |
| const char kContentType[] = "video/webm; codecs=\"vp09.00.10.08\""; |
| const char kAudioContentType[] = "audio/webm; codecs=\"opus\""; |
| const media::VideoCodecProfile kCodecProfile = media::VP9PROFILE_PROFILE0; |
| const media::VideoCodec kCodec = media::kCodecVP9; |
| const double kFramerate = 20.5; |
| const int kWidth = 3840; |
| const int kHeight = 2160; |
| const int kBitrate = 2391000; |
| |
| // Construct VideoConfig using the constants above. |
| MediaDecodingConfiguration* CreateAudioDecodingConfig() { |
| auto* audio_config = MakeGarbageCollected<AudioConfiguration>(); |
| audio_config->setContentType(kAudioContentType); |
| auto* decoding_config = MakeGarbageCollected<MediaDecodingConfiguration>(); |
| decoding_config->setType("media-source"); |
| decoding_config->setAudio(audio_config); |
| return decoding_config; |
| } |
| |
| // Construct VideoConfig using the constants above. |
| MediaDecodingConfiguration* CreateDecodingConfig() { |
| auto* video_config = MakeGarbageCollected<VideoConfiguration>(); |
| video_config->setFramerate(kFramerate); |
| video_config->setContentType(kContentType); |
| video_config->setWidth(kWidth); |
| video_config->setHeight(kHeight); |
| video_config->setBitrate(kBitrate); |
| auto* decoding_config = MakeGarbageCollected<MediaDecodingConfiguration>(); |
| decoding_config->setType("media-source"); |
| decoding_config->setVideo(video_config); |
| return decoding_config; |
| } |
| |
| // Construct PredicitonFeatures matching the CreateDecodingConfig, using the |
| // constants above. |
| media::mojom::blink::PredictionFeatures CreateFeatures() { |
| media::mojom::blink::PredictionFeatures features; |
| features.profile = |
| static_cast<media::mojom::blink::VideoCodecProfile>(kCodecProfile); |
| features.video_size = gfx::Size(kWidth, kHeight); |
| features.frames_per_sec = kFramerate; |
| |
| // Not set by any tests so far. Choosing sane defaults to mirror production |
| // code. |
| features.key_system = ""; |
| features.use_hw_secure_codecs = false; |
| |
| return features; |
| } |
| |
| Vector<media::learning::FeatureValue> CreateFeaturesML() { |
| media::mojom::blink::PredictionFeatures features = CreateFeatures(); |
| |
| // FRAGILE: Order here MUST match order in |
| // WebMediaPlayerImpl::UpdateSmoothnessHelper(). |
| // TODO(chcunningham): refactor into something more robust. |
| Vector<media::learning::FeatureValue> ml_features( |
| {media::learning::FeatureValue(kCodec), |
| media::learning::FeatureValue(kCodecProfile), |
| media::learning::FeatureValue(kWidth), |
| media::learning::FeatureValue(kFramerate)}); |
| |
| return ml_features; |
| } |
| |
| // Types of smoothness predictions. |
| enum class PredictionType { |
| kDB, |
| kBadWindow, |
| kNnr, |
| kGpuFactories, |
| }; |
| |
| // Makes a TargetHistogram with single count at |target_value|. |
| media::learning::TargetHistogram MakeHistogram(double target_value) { |
| media::learning::TargetHistogram histogram; |
| histogram += media::learning::TargetValue(target_value); |
| return histogram; |
| } |
| |
| // Makes DB (PerfHistoryService) callback for use with gtest WillOnce(). |
| // Callback will verify |features| matches |expected_features| and run with |
| // provided values for |is_smooth| and |is_power_efficient|. |
| testing::Action<void(media::mojom::blink::PredictionFeaturesPtr, |
| MockPerfHistoryService::GetPerfInfoCallback)> |
| DbCallback(const media::mojom::blink::PredictionFeatures& expected_features, |
| bool is_smooth, |
| bool is_power_efficient) { |
| return [=](media::mojom::blink::PredictionFeaturesPtr features, |
| MockPerfHistoryService::GetPerfInfoCallback got_info_cb) { |
| EXPECT_TRUE(features->Equals(expected_features)); |
| std::move(got_info_cb).Run(is_smooth, is_power_efficient); |
| }; |
| } |
| |
| // Makes ML (LearningTaskControllerService) callback for use with gtest |
| // WillOnce(). Callback will verify |features| matches |expected_features| and |
| // run a TargetHistogram containing a single count for |histogram_target|. |
| testing::Action<void( |
| const Vector<media::learning::FeatureValue>&, |
| MockLearningTaskControllerService::PredictDistributionCallback predict_cb)> |
| MlCallback(const Vector<media::learning::FeatureValue>& expected_features, |
| double histogram_target) { |
| return [=](const Vector<media::learning::FeatureValue>& features, |
| MockLearningTaskControllerService::PredictDistributionCallback |
| predict_cb) { |
| EXPECT_EQ(features, expected_features); |
| std::move(predict_cb).Run(MakeHistogram(histogram_target)); |
| }; |
| } |
| |
| testing::Action<void(base::OnceClosure)> GpuFactoriesNotifyCallback() { |
| return [](base::OnceClosure cb) { std::move(cb).Run(); }; |
| } |
| |
| // Helper to constructs field trial params with given ML prediction thresholds. |
| base::FieldTrialParams MakeMlParams(double bad_window_threshold, |
| double nnr_threshold) { |
| base::FieldTrialParams params; |
| params[MediaCapabilities::kLearningBadWindowThresholdParamName] = |
| base::NumberToString(bad_window_threshold); |
| params[MediaCapabilities::kLearningNnrThresholdParamName] = |
| base::NumberToString(nnr_threshold); |
| return params; |
| } |
| |
| // Wrapping deocdingInfo() call for readability. Await resolution of the promise |
| // and return its info. |
| MediaCapabilitiesInfo* DecodingInfo( |
| const MediaDecodingConfiguration* decoding_config, |
| MediaCapabilitiesTestContext* context) { |
| ScriptPromise promise = context->GetMediaCapabilities()->decodingInfo( |
| context->GetScriptState(), decoding_config, context->GetExceptionState()); |
| |
| ScriptPromiseTester tester(context->GetScriptState(), promise); |
| tester.WaitUntilSettled(); |
| |
| CHECK(!tester.IsRejected()) << " Cant get info from rejected promise."; |
| |
| return NativeValueTraits<MediaCapabilitiesInfo>::NativeValue( |
| context->GetIsolate(), tester.Value().V8Value(), |
| context->GetExceptionState()); |
| } |
| |
| } // namespace |
| |
| TEST(MediaCapabilitiesTests, BasicAudio) { |
| MediaCapabilitiesTestContext context; |
| const MediaDecodingConfiguration* kDecodingConfig = |
| CreateAudioDecodingConfig(); |
| MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->supported()); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| } |
| |
| // Other tests will assume these match. Test to be sure they stay in sync. |
| TEST(MediaCapabilitiesTests, ConfigMatchesFeatures) { |
| const MediaDecodingConfiguration* kDecodingConfig = CreateDecodingConfig(); |
| const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures(); |
| |
| EXPECT_TRUE(kDecodingConfig->video()->contentType().Contains("vp09.00")); |
| EXPECT_EQ(static_cast<media::VideoCodecProfile>(kFeatures.profile), |
| media::VP9PROFILE_PROFILE0); |
| EXPECT_EQ(kCodecProfile, media::VP9PROFILE_PROFILE0); |
| |
| EXPECT_EQ(kDecodingConfig->video()->framerate(), kFeatures.frames_per_sec); |
| EXPECT_EQ(kDecodingConfig->video()->width(), |
| static_cast<uint32_t>(kFeatures.video_size.width())); |
| EXPECT_EQ(kDecodingConfig->video()->height(), |
| static_cast<uint32_t>(kFeatures.video_size.height())); |
| } |
| |
| // Test that non-integer framerate isn't truncated by IPC. |
| // https://crbug.com/1024399 |
| TEST(MediaCapabilitiesTests, NonIntegerFramerate) { |
| MediaCapabilitiesTestContext context; |
| |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| // Enabled features. |
| {}, |
| // Disabled ML predictions + GpuFactories (just use DB). |
| {media::kMediaCapabilitiesQueryGpuFactories, |
| media::kMediaLearningSmoothnessExperiment}); |
| |
| const auto* kDecodingConfig = CreateDecodingConfig(); |
| const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures(); |
| |
| // FPS for this test must not be a whole number. Assert to ensure the default |
| // config meets that condition. |
| ASSERT_NE(fmod(kDecodingConfig->video()->framerate(), 1), 0); |
| |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce([&](media::mojom::blink::PredictionFeaturesPtr features, |
| MockPerfHistoryService::GetPerfInfoCallback got_info_cb) { |
| // Explicitly check for frames_per_sec equality. |
| // PredictionFeatures::Equals() will not catch loss of precision if |
| // frames_per_sec is made to be int (currently a double). |
| EXPECT_EQ(features->frames_per_sec, kFramerate); |
| |
| // Check that other things match as well. |
| EXPECT_TRUE(features->Equals(kFeatures)); |
| |
| std::move(got_info_cb).Run(/*smooth*/ true, /*power_efficient*/ true); |
| }); |
| |
| MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| } |
| |
| // Test smoothness predictions from DB (PerfHistoryService). |
| TEST(MediaCapabilitiesTests, PredictWithJustDB) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| // Enabled features. |
| {}, |
| // Disabled ML predictions + GpuFactories (just use DB). |
| {media::kMediaCapabilitiesQueryGpuFactories, |
| media::kMediaLearningSmoothnessExperiment}); |
| |
| MediaCapabilitiesTestContext context; |
| const auto* kDecodingConfig = CreateDecodingConfig(); |
| const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures(); |
| |
| // ML services should not be called for prediction. |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .Times(0); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)).Times(0); |
| |
| // DB alone (PerfHistoryService) should be called. Signal smooth=true and |
| // power_efficient = false. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*power_eff*/ false)); |
| MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_FALSE(info->powerEfficient()); |
| |
| // Verify DB call was made. ML services should not even be bound. |
| testing::Mock::VerifyAndClearExpectations(context.GetPerfHistoryService()); |
| EXPECT_FALSE(context.GetBadWindowService()->is_bound()); |
| EXPECT_FALSE(context.GetNnrService()->is_bound()); |
| |
| // Repeat test with inverted smooth and power_efficient results. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*power_eff*/ true)); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| } |
| |
| TEST(MediaCapabilitiesTests, PredictPowerEfficientWithGpuFactories) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatures( |
| // Enable GpuFactories for power predictions. |
| {media::kMediaCapabilitiesQueryGpuFactories}, |
| // Disable ML predictions (may/may not be disabled by default). |
| {media::kMediaLearningSmoothnessExperiment}); |
| |
| MediaCapabilitiesTestContext context; |
| const auto* kDecodingConfig = CreateDecodingConfig(); |
| const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures(); |
| |
| // Setup DB to return powerEfficient = false. We later verify that opposite |
| // response from GpuFactories overrides the DB. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*power_eff*/ false)); |
| |
| auto mock_gpu_factories = |
| std::make_unique<media::MockGpuVideoAcceleratorFactories>(nullptr); |
| ON_CALL(context.GetMockPlatform(), GetGpuFactories()) |
| .WillByDefault(Return(mock_gpu_factories.get())); |
| |
| // First, lets simulate the scenario where we ask before support is known. The |
| // async path should notify us when the info arrives. We then get GpuFactroies |
| // again and learn the config is supported. |
| EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories()).Times(2); |
| { |
| // InSequence because we EXPECT two calls to IsDecoderSupportKnown with |
| // different return values. |
| InSequence s; |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown()) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(*mock_gpu_factories, NotifyDecoderSupportKnown(_)) |
| .WillOnce(GpuFactoriesNotifyCallback()); |
| |
| // MediaCapabilities calls IsDecoderSupportKnown() once, and |
| // GpuVideoAcceleratorFactories::IsDecoderConfigSupported() also calls it |
| // once internally. |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown()) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderConfigSupported(_, _)) |
| .WillOnce( |
| Return(media::GpuVideoAcceleratorFactories::Supported::kTrue)); |
| } |
| |
| // Info should be powerEfficient, preferring response of GpuFactories over |
| // the DB. |
| MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->powerEfficient()); |
| EXPECT_FALSE(info->smooth()); |
| context.VerifyAndClearMockExpectations(); |
| testing::Mock::VerifyAndClearExpectations(mock_gpu_factories.get()); |
| |
| // Now expect a second query with support is already known to be false. Set |
| // DB to respond with the opposite answer. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*power_eff*/ true)); |
| EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories()); |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown()) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderConfigSupported(_, _)) |
| .WillRepeatedly( |
| Return(media::GpuVideoAcceleratorFactories::Supported::kFalse)); |
| |
| // Info should be NOT powerEfficient, preferring response of GpuFactories over |
| // the DB. |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->powerEfficient()); |
| EXPECT_FALSE(info->smooth()); |
| context.VerifyAndClearMockExpectations(); |
| testing::Mock::VerifyAndClearExpectations(mock_gpu_factories.get()); |
| } |
| |
| // Test with smoothness predictions coming solely from "bad window" ML service. |
| TEST(MediaCapabilitiesTests, PredictWithBadWindowMLService) { |
| // Enable ML predictions with thresholds. -1 disables the NNR predictor. |
| const double kBadWindowThreshold = 2; |
| const double kNnrThreshold = -1; |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeaturesAndParameters( |
| // Enabled features w/ parameters |
| {{media::kMediaLearningSmoothnessExperiment, |
| MakeMlParams(kBadWindowThreshold, kNnrThreshold)}}, |
| // Disabled GpuFactories (use DB for power). |
| {media::kMediaCapabilitiesQueryGpuFactories}); |
| |
| MediaCapabilitiesTestContext context; |
| const auto* kDecodingConfig = CreateDecodingConfig(); |
| const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures(); |
| const Vector<media::learning::FeatureValue> kFeaturesML = CreateFeaturesML(); |
| |
| // ML is enabled, but DB should still be called for power efficiency (false). |
| // Its smoothness value (true) should be ignored in favor of ML prediction. |
| // Only bad window service should be asked for a prediction. Expect |
| // smooth=false because bad window prediction is equal to its threshold. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*efficient*/ false)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kBadWindowThreshold)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)).Times(0); |
| MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->smooth()); |
| EXPECT_FALSE(info->powerEfficient()); |
| // NNR service should not be bound when NNR predictions disabled. |
| EXPECT_FALSE(context.GetNnrService()->is_bound()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // Same as above, but invert all signals. Expect smooth=true because bad |
| // window prediction is now less than its threshold. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*efficient*/ true)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kBadWindowThreshold - 0.25)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)).Times(0); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| EXPECT_FALSE(context.GetNnrService()->is_bound()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // Same as above, but predict zero bad windows. Expect smooth=true because |
| // zero is below the threshold. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*efficient*/ true)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, /* bad windows */ 0)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)).Times(0); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| EXPECT_FALSE(context.GetNnrService()->is_bound()); |
| context.VerifyAndClearMockExpectations(); |
| } |
| |
| // Test with smoothness predictions coming solely from "NNR" ML service. |
| TEST(MediaCapabilitiesTests, PredictWithNnrMLService) { |
| // Enable ML predictions with thresholds. -1 disables the bad window |
| // predictor. |
| const double kBadWindowThreshold = -1; |
| const double kNnrThreshold = 5; |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeaturesAndParameters( |
| // Enabled both ML services. |
| {{media::kMediaLearningSmoothnessExperiment, |
| MakeMlParams(kBadWindowThreshold, kNnrThreshold)}}, |
| // Disabled features (use DB for power efficiency) |
| {media::kMediaCapabilitiesQueryGpuFactories}); |
| |
| MediaCapabilitiesTestContext context; |
| const auto* kDecodingConfig = CreateDecodingConfig(); |
| const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures(); |
| const Vector<media::learning::FeatureValue> kFeaturesML = CreateFeaturesML(); |
| |
| // ML is enabled, but DB should still be called for power efficiency (false). |
| // Its smoothness value (true) should be ignored in favor of ML prediction. |
| // Only NNR service should be asked for a prediction. Expect smooth=false |
| // because NNR prediction is equal to its threshold. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*efficient*/ false)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .Times(0); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kNnrThreshold)); |
| MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->smooth()); |
| EXPECT_FALSE(info->powerEfficient()); |
| // Bad window service should not be bound when NNR predictions disabled. |
| EXPECT_FALSE(context.GetBadWindowService()->is_bound()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // Same as above, but invert all signals. Expect smooth=true because NNR |
| // prediction is now less than its threshold. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*efficient*/ true)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .Times(0); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kNnrThreshold - 0.01)); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| EXPECT_FALSE(context.GetBadWindowService()->is_bound()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // Same as above, but predict zero NNRs. Expect smooth=true because zero is |
| // below the threshold. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*efficient*/ true)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .Times(0); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, /* NNRs */ 0)); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| EXPECT_FALSE(context.GetBadWindowService()->is_bound()); |
| context.VerifyAndClearMockExpectations(); |
| } |
| |
| // Test with combined smoothness predictions from both ML services. |
| TEST(MediaCapabilitiesTests, PredictWithBothMLServices) { |
| // Enable ML predictions with thresholds. |
| const double kBadWindowThreshold = 2; |
| const double kNnrThreshold = 1; |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeaturesAndParameters( |
| // Enabled both ML services. |
| {{media::kMediaLearningSmoothnessExperiment, |
| MakeMlParams(kBadWindowThreshold, kNnrThreshold)}}, |
| // Disabled features (use DB for power efficiency) |
| {media::kMediaCapabilitiesQueryGpuFactories}); |
| |
| MediaCapabilitiesTestContext context; |
| const auto* kDecodingConfig = CreateDecodingConfig(); |
| const media::mojom::blink::PredictionFeatures kFeatures = CreateFeatures(); |
| const Vector<media::learning::FeatureValue> kFeaturesML = CreateFeaturesML(); |
| |
| // ML is enabled, but DB should still be called for power efficiency (false). |
| // Its smoothness value (true) should be ignored in favor of ML predictions. |
| // Both ML services should be called for prediction. In both cases we exceed |
| // the threshold, such that smooth=false. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*efficient*/ false)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kBadWindowThreshold + 0.5)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kNnrThreshold + 0.5)); |
| MediaCapabilitiesInfo* info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->smooth()); |
| EXPECT_FALSE(info->powerEfficient()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // Make another call to DecodingInfo with one "bad window" prediction |
| // indicating smooth=false, while nnr prediction indicates smooth=true. Verify |
| // resulting info predicts false, as the logic should OR the false signals. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*efficient*/ false)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kBadWindowThreshold + 0.5)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kNnrThreshold / 2)); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->smooth()); |
| EXPECT_FALSE(info->powerEfficient()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // Same as above, but invert predictions from ML services. Outcome should |
| // still be smooth=false (logic is ORed). |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ true, /*efficient*/ false)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kBadWindowThreshold / 2)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kNnrThreshold + 0.5)); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->smooth()); |
| EXPECT_FALSE(info->powerEfficient()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // This time both ML services agree smooth=true while DB predicts |
| // smooth=false. Expect info->smooth() = true, as only ML predictions matter |
| // when ML experiment enabled. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*efficient*/ true)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kBadWindowThreshold / 2)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kNnrThreshold / 2)); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_TRUE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| context.VerifyAndClearMockExpectations(); |
| |
| // Same as above, but with ML services predicting exactly their respective |
| // thresholds. Now expect info->smooth() = false - reaching the threshold is |
| // considered not smooth. |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(DbCallback(kFeatures, /*smooth*/ false, /*efficient*/ true)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kBadWindowThreshold)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(MlCallback(kFeaturesML, kNnrThreshold)); |
| info = DecodingInfo(kDecodingConfig, &context); |
| EXPECT_FALSE(info->smooth()); |
| EXPECT_TRUE(info->powerEfficient()); |
| context.VerifyAndClearMockExpectations(); |
| } |
| |
| // Simulate a call to DecodingInfo with smoothness predictions arriving in the |
| // specified |callback_order|. Ensure that promise resolves correctly only after |
| // all callbacks have arrived. |
| void RunCallbackPermutationTest(std::vector<PredictionType> callback_order) { |
| // Enable ML predictions with thresholds. |
| const double kBadWindowThreshold = 2; |
| const double kNnrThreshold = 3; |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeaturesAndParameters( |
| // Enabled features w/ parameters |
| {{media::kMediaLearningSmoothnessExperiment, |
| MakeMlParams(kBadWindowThreshold, kNnrThreshold)}, |
| {media::kMediaCapabilitiesQueryGpuFactories, {}}}, |
| // Disabled features. |
| {}); |
| |
| MediaCapabilitiesTestContext context; |
| const auto* kDecodingConfig = CreateDecodingConfig(); |
| auto mock_gpu_factories = |
| std::make_unique<media::MockGpuVideoAcceleratorFactories>(nullptr); |
| |
| // DB and both ML services should be called. Save their callbacks. |
| CallbackSaver cb_saver; |
| EXPECT_CALL(*context.GetPerfHistoryService(), GetPerfInfo(_, _)) |
| .WillOnce(Invoke(&cb_saver, &CallbackSaver::SavePerfHistoryCallback)); |
| EXPECT_CALL(*context.GetBadWindowService(), PredictDistribution(_, _)) |
| .WillOnce(Invoke(&cb_saver, &CallbackSaver::SaveBadWindowCallback)); |
| EXPECT_CALL(*context.GetNnrService(), PredictDistribution(_, _)) |
| .WillOnce(Invoke(&cb_saver, &CallbackSaver::SaveNnrCallback)); |
| |
| // GpuFactories should also be called. Set it up to be async with arrival of |
| // support info. Save the "notify" callback. |
| EXPECT_CALL(context.GetMockPlatform(), GetGpuFactories()) |
| .WillRepeatedly(Return(mock_gpu_factories.get())); |
| { |
| // InSequence because we EXPECT two calls to IsDecoderSupportKnown with |
| // different return values. |
| InSequence s; |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown()) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(*mock_gpu_factories, NotifyDecoderSupportKnown(_)) |
| .WillOnce( |
| Invoke(&cb_saver, &CallbackSaver::SaveGpuFactoriesNotifyCallback)); |
| // MediaCapabilities calls IsDecoderSupportKnown() once, and |
| // GpuVideoAcceleratorFactories::IsDecoderConfigSupported() also calls it |
| // once internally. |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderSupportKnown()) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(*mock_gpu_factories, IsDecoderConfigSupported(_, _)) |
| .WillRepeatedly( |
| Return(media::GpuVideoAcceleratorFactories::Supported::kFalse)); |
| } |
| |
| // Call decodingInfo() to kick off the calls to prediction services. |
| ScriptPromise promise = context.GetMediaCapabilities()->decodingInfo( |
| context.GetScriptState(), kDecodingConfig, context.GetExceptionState()); |
| ScriptPromiseTester tester(context.GetScriptState(), promise); |
| |
| // Callbacks should all be saved after mojo's pending tasks have run. |
| test::RunPendingTasks(); |
| ASSERT_TRUE(cb_saver.perf_history_cb() && cb_saver.bad_window_cb() && |
| cb_saver.nnr_cb() && cb_saver.gpu_factories_notify_cb()); |
| |
| // Complete callbacks in whatever order. |
| for (size_t i = 0; i < callback_order.size(); ++i) { |
| switch (callback_order[i]) { |
| case PredictionType::kDB: |
| std::move(cb_saver.perf_history_cb()).Run(true, true); |
| break; |
| case PredictionType::kBadWindow: |
| std::move(cb_saver.bad_window_cb()) |
| .Run(MakeHistogram(kBadWindowThreshold - 0.25)); |
| break; |
| case PredictionType::kNnr: |
| std::move(cb_saver.nnr_cb()).Run(MakeHistogram(kNnrThreshold + 0.5)); |
| break; |
| case PredictionType::kGpuFactories: |
| std::move(cb_saver.gpu_factories_notify_cb()).Run(); |
| break; |
| } |
| |
| // Give callbacks/tasks a chance to run. |
| test::RunPendingTasks(); |
| |
| // Promise should only be resolved once the final callback has run. |
| if (i < callback_order.size() - 1) { |
| ASSERT_FALSE(tester.IsFulfilled()); |
| } else { |
| ASSERT_TRUE(tester.IsFulfilled()); |
| } |
| } |
| |
| ASSERT_FALSE(tester.IsRejected()) << " Cant get info from rejected promise."; |
| MediaCapabilitiesInfo* info = |
| NativeValueTraits<MediaCapabilitiesInfo>::NativeValue( |
| context.GetIsolate(), tester.Value().V8Value(), |
| context.GetExceptionState()); |
| |
| // Smooth=false because NNR prediction exceeds threshold. |
| EXPECT_FALSE(info->smooth()); |
| // DB predicted power_efficient = true, but GpuFactories overrides w/ false. |
| EXPECT_FALSE(info->powerEfficient()); |
| } |
| |
| // Test that decodingInfo() behaves correctly for all orderings/timings of the |
| // underlying prediction services. |
| TEST(MediaCapabilitiesTests, PredictionCallbackPermutations) { |
| std::vector<PredictionType> callback_order( |
| {PredictionType::kDB, PredictionType::kBadWindow, PredictionType::kNnr, |
| PredictionType::kGpuFactories}); |
| do { |
| RunCallbackPermutationTest(callback_order); |
| } while (std::next_permutation(callback_order.begin(), callback_order.end())); |
| } |
| |
| } // namespace blink |