OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <array> | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/logging.h" | |
10 #include "base/test/test_simple_task_runner.h" | |
11 #include "content/renderer/media/audio_renderer_sink_cache_impl.h" | |
12 #include "media/audio/audio_device_description.h" | |
13 #include "media/base/audio_parameters.h" | |
14 #include "media/base/mock_audio_renderer_sink.h" | |
15 #include "media/base/test_helpers.h" | |
16 #include "testing/gmock/include/gmock/gmock.h" | |
17 #include "testing/gtest/include/gtest/gtest.h" | |
18 #include "url/gurl.h" | |
19 | |
20 namespace content { | |
21 | |
22 namespace { | |
DaleCurtis
2016/05/26 19:18:09
If you convert to an anonymous namespace, drop the
o1ka
2016/05/27 13:48:52
Done.
| |
23 static const char* const kDefaultDeviceId = | |
24 media::AudioDeviceDescription::kDefaultDeviceId; | |
25 static const char kAnotherDeviceId[] = "another-device-id"; | |
26 static const int kRenderFrameId = 124; | |
27 static const int kDeleteTimeoutMs = 1000; | |
28 } // namespace | |
29 | |
30 class AudioRendererSinkCacheTest : public testing::Test { | |
31 public: | |
32 AudioRendererSinkCacheTest() | |
33 : cache_(new AudioRendererSinkCacheImpl( | |
34 message_loop_.task_runner(), | |
35 base::Bind(&AudioRendererSinkCacheTest::CreateSink, | |
36 base::Unretained(this)), | |
37 base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs))) {} | |
38 | |
39 void GetSink(int render_frame_id, | |
40 const std::string& device_id, | |
41 const url::Origin& security_origin, | |
42 media::AudioRendererSink** sink) { | |
43 *sink = cache_->GetSink(render_frame_id, device_id, security_origin).get(); | |
44 } | |
45 | |
46 void GetRandomSinkInfo(int frame) { | |
47 // Get info and check if memory is not corrupted. | |
48 EXPECT_EQ(kDefaultDeviceId, | |
49 cache_->GetSinkInfo(frame, 0, kDefaultDeviceId, url::Origin()) | |
50 .device_id()); | |
51 } | |
52 | |
53 void GetRandomSink(int frame, base::TimeDelta sleep_timeout) { | |
54 scoped_refptr<media::AudioRendererSink> sink = | |
55 cache_->GetSink(frame, kDefaultDeviceId, url::Origin()).get(); | |
56 ExpectToStop(sink.get()); | |
57 base::PlatformThread::Sleep(sleep_timeout); | |
58 cache_->ReleaseSink(sink.get()); | |
59 sink->Stop(); // Call a method to make the object is not corrupted. | |
60 } | |
61 | |
62 protected: | |
63 int sink_count() { | |
64 DCHECK(message_loop_.task_runner()->BelongsToCurrentThread()); | |
65 return cache_->GetCacheSizeForTesting(); | |
66 } | |
67 | |
68 scoped_refptr<media::AudioRendererSink> CreateSink( | |
69 int render_frame_id, | |
70 int session_id, | |
71 const std::string& device_id, | |
72 const url::Origin& security_origin) { | |
73 return new media::MockAudioRendererSink(device_id, | |
74 media::OUTPUT_DEVICE_STATUS_OK); | |
75 } | |
76 | |
77 void ExpectToStop(media::AudioRendererSink* sink) { | |
78 // Sink must be stoped before deletion. | |
79 EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop()) | |
80 .Times(1); | |
81 } | |
82 | |
83 void ExpectNotToStop(media::AudioRendererSink* sink) { | |
84 // The sink must be stoped before deletion. | |
85 EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink), Stop()) | |
86 .Times(0); | |
87 } | |
88 | |
89 // Posts the task to the specified thread and runs current message loop until | |
90 // the task is completed. | |
91 void PostAndRunUntilDone(const base::Thread& thread, | |
92 const base::Closure& task) { | |
93 media::WaitableMessageLoopEvent event; | |
94 thread.task_runner()->PostTaskAndReply(FROM_HERE, task, event.GetClosure()); | |
95 // Runs the loop and waits for the thread to call event's closure. | |
96 event.RunAndWait(); | |
97 } | |
98 | |
99 void WaitOnAnotherThread(const base::Thread& thread, int timeout_ms) { | |
100 PostAndRunUntilDone( | |
101 thread, base::Bind(base::IgnoreResult(&base::PlatformThread::Sleep), | |
102 base::TimeDelta::FromMilliseconds(timeout_ms))); | |
103 } | |
104 | |
105 base::MessageLoop message_loop_; | |
106 std::unique_ptr<AudioRendererSinkCacheImpl> cache_; | |
107 | |
108 private: | |
109 DISALLOW_COPY_AND_ASSIGN(AudioRendererSinkCacheTest); | |
110 }; | |
111 | |
112 // Verify that normal get/release sink sequence works. | |
113 TEST_F(AudioRendererSinkCacheTest, GetReleaseSink) { | |
114 // Verify that a new sink is successfully created. | |
115 EXPECT_EQ(0, sink_count()); | |
116 scoped_refptr<media::AudioRendererSink> sink = | |
117 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get(); | |
118 ExpectNotToStop(sink.get()); // Cache should not stop sinks marked as used. | |
119 EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id()); | |
120 EXPECT_EQ(1, sink_count()); | |
121 | |
122 // Verify that another sink with the same key is successfully created | |
123 scoped_refptr<media::AudioRendererSink> another_sink = | |
124 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get(); | |
125 ExpectNotToStop(another_sink.get()); | |
126 EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id()); | |
127 EXPECT_EQ(2, sink_count()); | |
128 EXPECT_NE(sink, another_sink); | |
129 | |
130 // Verify that another sink with a different kay is successfully created. | |
131 scoped_refptr<media::AudioRendererSink> yet_another_sink = | |
132 cache_->GetSink(kRenderFrameId, kAnotherDeviceId, url::Origin()).get(); | |
133 ExpectNotToStop(yet_another_sink.get()); | |
134 EXPECT_EQ(kAnotherDeviceId, | |
135 yet_another_sink->GetOutputDeviceInfo().device_id()); | |
136 EXPECT_EQ(3, sink_count()); | |
137 EXPECT_NE(sink, yet_another_sink); | |
138 EXPECT_NE(another_sink, yet_another_sink); | |
139 | |
140 // Verify that the first sink is successfully deleted. | |
141 cache_->ReleaseSink(sink.get()); | |
142 EXPECT_EQ(2, sink_count()); | |
143 sink = nullptr; | |
144 | |
145 // Make sure we deleted the right sink, and the memory for the rest is not | |
146 // corrupted. | |
147 EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id()); | |
148 EXPECT_EQ(kAnotherDeviceId, | |
149 yet_another_sink->GetOutputDeviceInfo().device_id()); | |
150 | |
151 // Verify that the second sink is successfully deleted. | |
152 cache_->ReleaseSink(another_sink.get()); | |
153 EXPECT_EQ(1, sink_count()); | |
154 EXPECT_EQ(kAnotherDeviceId, | |
155 yet_another_sink->GetOutputDeviceInfo().device_id()); | |
156 | |
157 cache_->ReleaseSink(yet_another_sink.get()); | |
158 EXPECT_EQ(0, sink_count()); | |
159 } | |
160 | |
161 // Verify that the sink created with GetSinkInfo() is reused when possible. | |
162 TEST_F(AudioRendererSinkCacheTest, GetDeviceInfo) { | |
163 EXPECT_EQ(0, sink_count()); | |
164 media::OutputDeviceInfo device_info = | |
165 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin()); | |
166 EXPECT_EQ(1, sink_count()); | |
167 | |
168 // The info on the same device is requested, so no new sink is created. | |
169 media::OutputDeviceInfo one_more_device_info = | |
170 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin()); | |
171 EXPECT_EQ(1, sink_count()); | |
172 EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id()); | |
173 | |
174 // Aquire the sink that was created on GetSinkInfo(). | |
175 scoped_refptr<media::AudioRendererSink> sink = | |
176 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get(); | |
177 EXPECT_EQ(1, sink_count()); | |
178 EXPECT_EQ(device_info.device_id(), sink->GetOutputDeviceInfo().device_id()); | |
179 | |
180 // Now the sink is in used, but we can still get the device info out of it, no | |
181 // new sink is created. | |
182 one_more_device_info = | |
183 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin()); | |
184 EXPECT_EQ(1, sink_count()); | |
185 EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id()); | |
186 | |
187 // Request sink for the same device. The first sink is in use, so a new one | |
188 // should be created. | |
189 scoped_refptr<media::AudioRendererSink> another_sink = | |
190 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get(); | |
191 EXPECT_EQ(2, sink_count()); | |
192 EXPECT_EQ(device_info.device_id(), | |
193 another_sink->GetOutputDeviceInfo().device_id()); | |
194 } | |
195 | |
196 // Verify that the sink created with GetSinkInfo() is deleted if unused. | |
197 // The test produces 2 "Uninteresting mock" warnings for | |
198 // MockAudioRendererSink::Stop(). | |
199 TEST_F(AudioRendererSinkCacheTest, GarbageCollection) { | |
200 EXPECT_EQ(0, sink_count()); | |
201 media::OutputDeviceInfo device_info = | |
202 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin()); | |
203 EXPECT_EQ(1, sink_count()); | |
204 | |
205 media::OutputDeviceInfo another_device_info = | |
206 cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin()); | |
207 EXPECT_EQ(2, sink_count()); | |
208 | |
209 base::Thread thread("timeout_thread"); | |
210 thread.Start(); | |
211 | |
212 // 100 ms more than garbage collection timeout. | |
213 WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100); | |
214 | |
215 // All the sinks should be garbage-collected by now. | |
216 EXPECT_EQ(0, sink_count()); | |
217 } | |
218 | |
219 // Verify that the sink created with GetSinkInfo() is not deleted if used within | |
220 // the timeout. | |
221 TEST_F(AudioRendererSinkCacheTest, NoGarbageCollectionForUsedSink) { | |
222 EXPECT_EQ(0, sink_count()); | |
223 media::OutputDeviceInfo device_info = | |
224 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin()); | |
225 EXPECT_EQ(1, sink_count()); | |
226 | |
227 base::Thread thread("timeout_thread"); | |
228 thread.Start(); | |
229 | |
230 // Wait significantly less than grabage collection timeout. | |
231 int wait_a_bit = 100; | |
232 DCHECK_GT(kDeleteTimeoutMs, wait_a_bit * 2); | |
233 WaitOnAnotherThread(thread, wait_a_bit); | |
234 | |
235 // Sink is not deleted yet. | |
236 EXPECT_EQ(1, sink_count()); | |
237 | |
238 // Request it: | |
239 scoped_refptr<media::AudioRendererSink> sink = | |
240 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get(); | |
241 EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id()); | |
242 EXPECT_EQ(1, sink_count()); | |
243 | |
244 // Wait more to hit garbage collection timeout. | |
245 WaitOnAnotherThread(thread, kDeleteTimeoutMs); | |
246 | |
247 // The sink is still in place. | |
248 EXPECT_EQ(1, sink_count()); | |
249 } | |
250 | |
251 // Verify that cache works fine if a sink scheduled for delettion is aquired and | |
252 // released before deletion timeout elapses. | |
253 // The test produces one "Uninteresting mock" warning for | |
254 // MockAudioRendererSink::Stop(). | |
255 TEST_F(AudioRendererSinkCacheTest, ReleaseSinkBeforeScheduledDeletion) { | |
256 EXPECT_EQ(0, sink_count()); | |
257 | |
258 media::OutputDeviceInfo device_info = | |
259 cache_->GetSinkInfo(kRenderFrameId, 0, kDefaultDeviceId, url::Origin()); | |
260 EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now. | |
261 | |
262 // Request it: | |
263 scoped_refptr<media::AudioRendererSink> sink = | |
264 cache_->GetSink(kRenderFrameId, kDefaultDeviceId, url::Origin()).get(); | |
265 ExpectNotToStop(sink.get()); | |
266 EXPECT_EQ(1, sink_count()); | |
267 | |
268 // Release it: | |
269 cache_->ReleaseSink(sink.get()); | |
270 EXPECT_EQ(0, sink_count()); | |
271 | |
272 media::OutputDeviceInfo another_device_info = | |
273 cache_->GetSinkInfo(kRenderFrameId, 0, kAnotherDeviceId, url::Origin()); | |
274 EXPECT_EQ(1, sink_count()); // This sink is scheduled for deletion now. | |
275 | |
276 base::Thread thread("timeout_thread"); | |
277 thread.Start(); | |
278 | |
279 // 100 ms more than garbage collection timeout. | |
280 WaitOnAnotherThread(thread, kDeleteTimeoutMs + 100); | |
281 | |
282 // Nothing crashed and the second sink deleted on schedule. | |
283 EXPECT_EQ(0, sink_count()); | |
284 } | |
285 | |
286 // Check that a sink created on one thread in response to GetSinkInfo can be | |
287 // used on another thread. | |
288 TEST_F(AudioRendererSinkCacheTest, MultithreadedAccess) { | |
289 EXPECT_EQ(0, sink_count()); | |
290 | |
291 base::Thread thread1("thread1"); | |
292 thread1.Start(); | |
293 | |
294 base::Thread thread2("thread2"); | |
295 thread2.Start(); | |
296 | |
297 // Request device information on the first thread. | |
298 PostAndRunUntilDone( | |
299 thread1, | |
300 base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo), | |
301 base::Unretained(cache_.get()), kRenderFrameId, 0, | |
302 kDefaultDeviceId, url::Origin())); | |
303 | |
304 EXPECT_EQ(1, sink_count()); | |
305 | |
306 // Request the sink on the second thread. | |
307 media::AudioRendererSink* sink; | |
308 | |
309 PostAndRunUntilDone( | |
310 thread2, | |
311 base::Bind(&AudioRendererSinkCacheTest::GetSink, base::Unretained(this), | |
312 kRenderFrameId, kDefaultDeviceId, url::Origin(), &sink)); | |
313 | |
314 EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id()); | |
315 EXPECT_EQ(1, sink_count()); | |
316 | |
317 // Request device information on the first thread again. | |
318 PostAndRunUntilDone( | |
319 thread1, | |
320 base::Bind(base::IgnoreResult(&AudioRendererSinkCacheImpl::GetSinkInfo), | |
321 base::Unretained(cache_.get()), kRenderFrameId, 0, | |
322 kDefaultDeviceId, url::Origin())); | |
323 EXPECT_EQ(1, sink_count()); | |
324 | |
325 // Release the sink on the second thread. | |
326 PostAndRunUntilDone(thread2, | |
327 base::Bind(&AudioRendererSinkCache::ReleaseSink, | |
328 base::Unretained(cache_.get()), sink)); | |
329 | |
330 EXPECT_EQ(0, sink_count()); | |
331 } | |
332 | |
333 // Intensive parallell access to the cache. Produces a ton of "Uninteresting | |
334 // mock" warnings for Stop() calls - this is fine. | |
335 TEST_F(AudioRendererSinkCacheTest, SmokeTest) { | |
336 const int kExperimentSize = 1000; | |
337 const int kSinkCount = 10; | |
338 const int kThreadCount = 3; | |
339 | |
340 // Sleep no more than (kDeleteTimeoutMs * 3) in total per thread. | |
341 const base::TimeDelta kSleepTimeout = | |
342 base::TimeDelta::FromMilliseconds(kDeleteTimeoutMs * 3 / kExperimentSize); | |
343 | |
344 srand(42); // Does not matter. | |
345 | |
346 std::array<std::unique_ptr<base::Thread>, kThreadCount> threads; | |
347 for (int i = 0; i < kThreadCount; ++i) { | |
348 threads[i].reset(new base::Thread(std::to_string(i))); | |
349 threads[i]->Start(); | |
350 } | |
351 | |
352 for (int i = 0; i < kExperimentSize; ++i) { | |
353 for (auto& thread : threads) { | |
354 thread->task_runner()->PostTask( | |
355 FROM_HERE, base::Bind(&AudioRendererSinkCacheTest::GetRandomSinkInfo, | |
356 base::Unretained(this), rand() % kSinkCount)); | |
357 thread->task_runner()->PostTask( | |
358 FROM_HERE, base::Bind(&AudioRendererSinkCacheTest::GetRandomSink, | |
359 base::Unretained(this), rand() % kSinkCount, | |
360 kSleepTimeout)); | |
361 } | |
362 } | |
363 | |
364 // Wait for completion of all the tasks posted to at least one thread. | |
365 media::WaitableMessageLoopEvent loop_event; | |
366 threads[kThreadCount - 1]->task_runner()->PostTaskAndReply( | |
367 FROM_HERE, base::Bind(&base::DoNothing), loop_event.GetClosure()); | |
368 // Runs the loop and waits for the thread to call event's closure. | |
369 loop_event.RunAndWait(); | |
370 } | |
371 | |
372 } // namespace content | |
OLD | NEW |