Index: content/browser/media/audio_stream_monitor_unittest.cc |
diff --git a/content/browser/media/audio_stream_monitor_unittest.cc b/content/browser/media/audio_stream_monitor_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b8871af65636e896ebe4b84eba3787ca67961b6a |
--- /dev/null |
+++ b/content/browser/media/audio_stream_monitor_unittest.cc |
@@ -0,0 +1,297 @@ |
+// Copyright 2014 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 "content/browser/media/audio_stream_monitor.h" |
+ |
+#include <map> |
+#include <utility> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/test/simple_test_tick_clock.h" |
+#include "content/browser/web_contents/web_contents_impl.h" |
+#include "content/public/browser/invalidate_type.h" |
+#include "content/public/browser/web_contents_delegate.h" |
+#include "content/public/test/test_renderer_host.h" |
+#include "media/audio/audio_power_monitor.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using ::testing::InvokeWithoutArgs; |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+const int kRenderProcessId = 1; |
+const int kAnotherRenderProcessId = 2; |
+const int kStreamId = 3; |
+const int kAnotherStreamId = 6; |
+ |
+// Used to confirm audio indicator state changes occur at the correct times. |
+class MockWebContentsDelegate : public WebContentsDelegate { |
+ public: |
+ MOCK_METHOD2(NavigationStateChanged, |
+ void(const WebContents* source, InvalidateTypes changed_flags)); |
+}; |
+ |
+} // namespace |
+ |
+class AudioStreamMonitorTest : public RenderViewHostTestHarness { |
+ public: |
+ AudioStreamMonitorTest() { |
+ // Start |clock_| at non-zero. |
+ clock_.Advance(base::TimeDelta::FromSeconds(1000000)); |
+ } |
+ |
+ virtual void SetUp() OVERRIDE { |
+ RenderViewHostTestHarness::SetUp(); |
+ |
+ WebContentsImpl* web_contents = reinterpret_cast<WebContentsImpl*>( |
+ RenderViewHostTestHarness::web_contents()); |
+ web_contents->SetDelegate(&mock_web_contents_delegate_); |
+ monitor_ = web_contents->audio_stream_monitor(); |
+ const_cast<base::TickClock*&>(monitor_->clock_) = &clock_; |
+ } |
+ |
+ base::TimeTicks GetTestClockTime() { return clock_.NowTicks(); } |
+ |
+ void AdvanceClock(const base::TimeDelta& delta) { clock_.Advance(delta); } |
+ |
+ AudioStreamMonitor::ReadPowerAndClipCallback CreatePollCallback( |
+ int stream_id) { |
+ return base::Bind( |
+ &AudioStreamMonitorTest::ReadPower, base::Unretained(this), stream_id); |
+ } |
+ |
+ void SetStreamPower(int stream_id, float power) { |
+ current_power_[stream_id] = power; |
+ } |
+ |
+ void SimulatePollTimerFired() { monitor_->Poll(); } |
+ |
+ void SimulateOffTimerFired() { monitor_->MaybeToggle(); } |
+ |
+ void ExpectIsPolling(int render_process_id, int stream_id, bool is_polling) { |
+ const AudioStreamMonitor::StreamID key(render_process_id, stream_id); |
+ EXPECT_EQ( |
+ is_polling, |
+ monitor_->poll_callbacks_.find(key) != monitor_->poll_callbacks_.end()); |
+ EXPECT_EQ(!monitor_->poll_callbacks_.empty(), |
+ monitor_->poll_timer_.IsRunning()); |
+ } |
+ |
+ void ExpectTabWasRecentlyAudible(bool was_audible, |
+ const base::TimeTicks& last_blurt_time) { |
+ EXPECT_EQ(was_audible, monitor_->was_recently_audible_); |
+ EXPECT_EQ(last_blurt_time, monitor_->last_blurt_time_); |
+ EXPECT_EQ(monitor_->was_recently_audible_, |
+ monitor_->off_timer_.IsRunning()); |
+ } |
+ |
+ void ExpectWebContentsWillBeNotifiedOnce(bool should_be_audible) { |
+ EXPECT_CALL( |
+ mock_web_contents_delegate_, |
+ NavigationStateChanged(RenderViewHostTestHarness::web_contents(), |
+ INVALIDATE_TYPE_TAB)) |
+ .WillOnce(InvokeWithoutArgs( |
+ this, |
+ should_be_audible |
+ ? &AudioStreamMonitorTest::ExpectIsNotifyingForToggleOn |
+ : &AudioStreamMonitorTest::ExpectIsNotifyingForToggleOff)) |
+ .RetiresOnSaturation(); |
+ } |
+ |
+ static base::TimeDelta one_polling_interval() { |
+ return base::TimeDelta::FromSeconds(1) / |
+ AudioStreamMonitor::kPowerMeasurementsPerSecond; |
+ } |
+ |
+ static base::TimeDelta holding_period() { |
+ return base::TimeDelta::FromMilliseconds( |
+ AudioStreamMonitor::kHoldOnMilliseconds); |
+ } |
+ |
+ void StartMonitoring( |
+ int render_process_id, |
+ int stream_id, |
+ const AudioStreamMonitor::ReadPowerAndClipCallback& callback) { |
+ monitor_->StartMonitoringStreamOnUIThread( |
+ render_process_id, stream_id, callback); |
+ } |
+ |
+ void StopMonitoring(int render_process_id, int stream_id) { |
+ monitor_->StopMonitoringStreamOnUIThread(render_process_id, stream_id); |
+ } |
+ |
+ protected: |
+ AudioStreamMonitor* monitor_; |
+ |
+ private: |
+ std::pair<float, bool> ReadPower(int stream_id) { |
+ return std::make_pair(current_power_[stream_id], false); |
+ } |
+ |
+ void ExpectIsNotifyingForToggleOn() { |
+ EXPECT_TRUE(monitor_->WasRecentlyAudible()); |
+ } |
+ |
+ void ExpectIsNotifyingForToggleOff() { |
+ EXPECT_FALSE(monitor_->WasRecentlyAudible()); |
+ } |
+ |
+ MockWebContentsDelegate mock_web_contents_delegate_; |
+ base::SimpleTestTickClock clock_; |
+ std::map<int, float> current_power_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AudioStreamMonitorTest); |
+}; |
+ |
+// Tests that AudioStreamMonitor is polling while it has a |
+// ReadPowerAndClipCallback, and is not polling at other times. |
+TEST_F(AudioStreamMonitorTest, PollsWhenProvidedACallback) { |
+ EXPECT_FALSE(monitor_->WasRecentlyAudible()); |
+ ExpectIsPolling(kRenderProcessId, kStreamId, false); |
+ |
+ StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); |
+ EXPECT_FALSE(monitor_->WasRecentlyAudible()); |
+ ExpectIsPolling(kRenderProcessId, kStreamId, true); |
+ |
+ StopMonitoring(kRenderProcessId, kStreamId); |
+ EXPECT_FALSE(monitor_->WasRecentlyAudible()); |
+ ExpectIsPolling(kRenderProcessId, kStreamId, false); |
+} |
+ |
+// Tests that AudioStreamMonitor debounces the power level readings it's taking, |
+// which could be fluctuating rapidly between the audible versus silence |
+// threshold. See comments in audio_stream_monitor.h for expected behavior. |
+TEST_F(AudioStreamMonitorTest, |
+ ImpulsesKeepIndicatorOnUntilHoldingPeriodHasPassed) { |
+ StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); |
+ |
+ // Expect WebContents will get one call form AudioStreamMonitor to toggle the |
+ // indicator on upon the very first poll. |
+ ExpectWebContentsWillBeNotifiedOnce(true); |
+ |
+ // Loop, each time testing a slightly longer period of polled silence. The |
+ // indicator should remain on throughout. |
+ int num_silence_polls = 0; |
+ base::TimeTicks last_blurt_time; |
+ do { |
+ // Poll an audible signal, and expect tab indicator state is on. |
+ SetStreamPower(kStreamId, media::AudioPowerMonitor::max_power()); |
+ last_blurt_time = GetTestClockTime(); |
+ SimulatePollTimerFired(); |
+ ExpectTabWasRecentlyAudible(true, last_blurt_time); |
+ AdvanceClock(one_polling_interval()); |
+ |
+ // Poll a silent signal repeatedly, ensuring that the indicator is being |
+ // held on during the holding period. |
+ SetStreamPower(kStreamId, media::AudioPowerMonitor::zero_power()); |
+ for (int i = 0; i < num_silence_polls; ++i) { |
+ SimulatePollTimerFired(); |
+ ExpectTabWasRecentlyAudible(true, last_blurt_time); |
+ // Note: Redundant off timer firings should not have any effect. |
+ SimulateOffTimerFired(); |
+ ExpectTabWasRecentlyAudible(true, last_blurt_time); |
+ AdvanceClock(one_polling_interval()); |
+ } |
+ |
+ ++num_silence_polls; |
+ } while (GetTestClockTime() < last_blurt_time + holding_period()); |
+ |
+ // At this point, the clock has just advanced to beyond the holding period, so |
+ // the next firing of the off timer should turn off the tab indicator. Also, |
+ // make sure it stays off for several cycles thereafter. |
+ ExpectWebContentsWillBeNotifiedOnce(false); |
+ for (int i = 0; i < 10; ++i) { |
+ SimulateOffTimerFired(); |
+ ExpectTabWasRecentlyAudible(false, last_blurt_time); |
+ AdvanceClock(one_polling_interval()); |
+ } |
+} |
+ |
+// Tests that the AudioStreamMonitor correctly processes the blurts from two |
+// different streams in the same tab. |
+TEST_F(AudioStreamMonitorTest, HandlesMultipleStreamsBlurting) { |
+ StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); |
+ StartMonitoring( |
+ kRenderProcessId, kAnotherStreamId, CreatePollCallback(kAnotherStreamId)); |
+ |
+ base::TimeTicks last_blurt_time; |
+ ExpectTabWasRecentlyAudible(false, last_blurt_time); |
+ |
+ // Measure audible sound from the first stream and silence from the second. |
+ // The indicator turns on (i.e., tab was recently audible). |
+ ExpectWebContentsWillBeNotifiedOnce(true); |
+ SetStreamPower(kStreamId, media::AudioPowerMonitor::max_power()); |
+ SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power()); |
+ last_blurt_time = GetTestClockTime(); |
+ SimulatePollTimerFired(); |
+ ExpectTabWasRecentlyAudible(true, last_blurt_time); |
+ |
+ // Halfway through the holding period, the second stream joins in. The |
+ // indicator stays on. |
+ AdvanceClock(holding_period() / 2); |
+ SimulateOffTimerFired(); |
+ SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::max_power()); |
+ last_blurt_time = GetTestClockTime(); |
+ SimulatePollTimerFired(); // Restarts holding period. |
+ ExpectTabWasRecentlyAudible(true, last_blurt_time); |
+ |
+ // Now, measure silence from both streams. After an entire holding period |
+ // has passed (since the second stream joined in), the indicator should turn |
+ // off. |
+ ExpectWebContentsWillBeNotifiedOnce(false); |
+ AdvanceClock(holding_period()); |
+ SimulateOffTimerFired(); |
+ SetStreamPower(kStreamId, media::AudioPowerMonitor::zero_power()); |
+ SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power()); |
+ SimulatePollTimerFired(); |
+ ExpectTabWasRecentlyAudible(false, last_blurt_time); |
+ |
+ // Now, measure silence from the first stream and audible sound from the |
+ // second. The indicator turns back on. |
+ ExpectWebContentsWillBeNotifiedOnce(true); |
+ SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::max_power()); |
+ last_blurt_time = GetTestClockTime(); |
+ SimulatePollTimerFired(); |
+ ExpectTabWasRecentlyAudible(true, last_blurt_time); |
+ |
+ // From here onwards, both streams are silent. Halfway through the holding |
+ // period, the indicator should not have changed. |
+ SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power()); |
+ AdvanceClock(holding_period() / 2); |
+ SimulatePollTimerFired(); |
+ SimulateOffTimerFired(); |
+ ExpectTabWasRecentlyAudible(true, last_blurt_time); |
+ |
+ // Just past the holding period, the indicator should be turned off. |
+ ExpectWebContentsWillBeNotifiedOnce(false); |
+ AdvanceClock(holding_period() - (GetTestClockTime() - last_blurt_time)); |
+ SimulateOffTimerFired(); |
+ ExpectTabWasRecentlyAudible(false, last_blurt_time); |
+ |
+ // Polling should not turn the indicator back while both streams are remaining |
+ // silent. |
+ for (int i = 0; i < 100; ++i) { |
+ AdvanceClock(one_polling_interval()); |
+ SimulatePollTimerFired(); |
+ ExpectTabWasRecentlyAudible(false, last_blurt_time); |
+ } |
+} |
+ |
+TEST_F(AudioStreamMonitorTest, MultipleRendererProcesses) { |
+ StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); |
+ StartMonitoring( |
+ kAnotherRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); |
+ ExpectIsPolling(kRenderProcessId, kStreamId, true); |
+ ExpectIsPolling(kAnotherRenderProcessId, kStreamId, true); |
+ StopMonitoring(kAnotherRenderProcessId, kStreamId); |
+ ExpectIsPolling(kRenderProcessId, kStreamId, true); |
+ ExpectIsPolling(kAnotherRenderProcessId, kStreamId, false); |
+} |
+ |
+} // namespace content |