OLD | NEW |
| (Empty) |
1 /* | |
2 * libjingle | |
3 * Copyright 2013 Google Inc. | |
4 * | |
5 * Redistribution and use in source and binary forms, with or without | |
6 * modification, are permitted provided that the following conditions are met: | |
7 * | |
8 * 1. Redistributions of source code must retain the above copyright notice, | |
9 * this list of conditions and the following disclaimer. | |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | |
11 * this list of conditions and the following disclaimer in the documentation | |
12 * and/or other materials provided with the distribution. | |
13 * 3. The name of the author may not be used to endorse or promote products | |
14 * derived from this software without specific prior written permission. | |
15 * | |
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | |
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
26 */ | |
27 | |
28 package org.webrtc; | |
29 | |
30 import org.webrtc.PeerConnection.IceConnectionState; | |
31 import org.webrtc.PeerConnection.IceGatheringState; | |
32 import org.webrtc.PeerConnection.SignalingState; | |
33 | |
34 import java.io.File; | |
35 import java.lang.ref.WeakReference; | |
36 import java.nio.ByteBuffer; | |
37 import java.nio.charset.Charset; | |
38 import java.util.Arrays; | |
39 import java.util.IdentityHashMap; | |
40 import java.util.LinkedList; | |
41 import java.util.List; | |
42 import java.util.Map; | |
43 import java.util.TreeSet; | |
44 import java.util.concurrent.CountDownLatch; | |
45 import java.util.concurrent.TimeUnit; | |
46 | |
47 import static junit.framework.Assert.*; | |
48 | |
49 /** End-to-end tests for PeerConnection.java. */ | |
50 public class PeerConnectionTest { | |
51 // Set to true to render video. | |
52 private static final boolean RENDER_TO_GUI = false; | |
53 private static final int TIMEOUT_SECONDS = 20; | |
54 private TreeSet<String> threadsBeforeTest = null; | |
55 | |
56 private static class ObserverExpectations implements PeerConnection.Observer, | |
57 VideoRenderer.Callbacks, | |
58 DataChannel.Observer, | |
59 StatsObserver { | |
60 private final String name; | |
61 private int expectedIceCandidates = 0; | |
62 private int expectedErrors = 0; | |
63 private int expectedRenegotiations = 0; | |
64 private int previouslySeenWidth = 0; | |
65 private int previouslySeenHeight = 0; | |
66 private int expectedFramesDelivered = 0; | |
67 private LinkedList<SignalingState> expectedSignalingChanges = | |
68 new LinkedList<SignalingState>(); | |
69 private LinkedList<IceConnectionState> expectedIceConnectionChanges = | |
70 new LinkedList<IceConnectionState>(); | |
71 private LinkedList<IceGatheringState> expectedIceGatheringChanges = | |
72 new LinkedList<IceGatheringState>(); | |
73 private LinkedList<String> expectedAddStreamLabels = | |
74 new LinkedList<String>(); | |
75 private LinkedList<String> expectedRemoveStreamLabels = | |
76 new LinkedList<String>(); | |
77 private final LinkedList<IceCandidate> gotIceCandidates = | |
78 new LinkedList<IceCandidate>(); | |
79 private Map<MediaStream, WeakReference<VideoRenderer>> renderers = | |
80 new IdentityHashMap<MediaStream, WeakReference<VideoRenderer>>(); | |
81 private DataChannel dataChannel; | |
82 private LinkedList<DataChannel.Buffer> expectedBuffers = | |
83 new LinkedList<DataChannel.Buffer>(); | |
84 private LinkedList<DataChannel.State> expectedStateChanges = | |
85 new LinkedList<DataChannel.State>(); | |
86 private LinkedList<String> expectedRemoteDataChannelLabels = | |
87 new LinkedList<String>(); | |
88 private int expectedStatsCallbacks = 0; | |
89 private LinkedList<StatsReport[]> gotStatsReports = | |
90 new LinkedList<StatsReport[]>(); | |
91 | |
92 public ObserverExpectations(String name) { | |
93 this.name = name; | |
94 } | |
95 | |
96 public synchronized void setDataChannel(DataChannel dataChannel) { | |
97 assertNull(this.dataChannel); | |
98 this.dataChannel = dataChannel; | |
99 this.dataChannel.registerObserver(this); | |
100 assertNotNull(this.dataChannel); | |
101 } | |
102 | |
103 public synchronized void expectIceCandidates(int count) { | |
104 expectedIceCandidates += count; | |
105 } | |
106 | |
107 @Override | |
108 public synchronized void onIceCandidate(IceCandidate candidate) { | |
109 --expectedIceCandidates; | |
110 | |
111 // We don't assert expectedIceCandidates >= 0 because it's hard to know | |
112 // how many to expect, in general. We only use expectIceCandidates to | |
113 // assert a minimal count. | |
114 synchronized (gotIceCandidates) { | |
115 gotIceCandidates.add(candidate); | |
116 gotIceCandidates.notifyAll(); | |
117 } | |
118 } | |
119 | |
120 private synchronized void setSize(int width, int height) { | |
121 assertFalse(RENDER_TO_GUI); | |
122 // Because different camera devices (fake & physical) produce different | |
123 // resolutions, we only sanity-check the set sizes, | |
124 assertTrue(width > 0); | |
125 assertTrue(height > 0); | |
126 if (previouslySeenWidth > 0) { | |
127 assertEquals(previouslySeenWidth, width); | |
128 assertEquals(previouslySeenHeight, height); | |
129 } else { | |
130 previouslySeenWidth = width; | |
131 previouslySeenHeight = height; | |
132 } | |
133 } | |
134 | |
135 public synchronized void expectFramesDelivered(int count) { | |
136 assertFalse(RENDER_TO_GUI); | |
137 expectedFramesDelivered += count; | |
138 } | |
139 | |
140 @Override | |
141 public synchronized void renderFrame(VideoRenderer.I420Frame frame) { | |
142 setSize(frame.rotatedWidth(), frame.rotatedHeight()); | |
143 --expectedFramesDelivered; | |
144 VideoRenderer.renderFrameDone(frame); | |
145 } | |
146 | |
147 public synchronized void expectSignalingChange(SignalingState newState) { | |
148 expectedSignalingChanges.add(newState); | |
149 } | |
150 | |
151 @Override | |
152 public synchronized void onSignalingChange(SignalingState newState) { | |
153 assertEquals(expectedSignalingChanges.removeFirst(), newState); | |
154 } | |
155 | |
156 public synchronized void expectIceConnectionChange( | |
157 IceConnectionState newState) { | |
158 expectedIceConnectionChanges.add(newState); | |
159 } | |
160 | |
161 @Override | |
162 public synchronized void onIceConnectionChange( | |
163 IceConnectionState newState) { | |
164 // TODO(bemasc): remove once delivery of ICECompleted is reliable | |
165 // (https://code.google.com/p/webrtc/issues/detail?id=3021). | |
166 if (newState.equals(IceConnectionState.COMPLETED)) { | |
167 return; | |
168 } | |
169 | |
170 if (expectedIceConnectionChanges.isEmpty()) { | |
171 System.out.println(name + "Got an unexpected ice connection change " + n
ewState); | |
172 return; | |
173 } | |
174 | |
175 assertEquals(expectedIceConnectionChanges.removeFirst(), newState); | |
176 } | |
177 | |
178 @Override | |
179 public synchronized void onIceConnectionReceivingChange(boolean receiving) { | |
180 System.out.println(name + "Got an ice connection receiving change " + rece
iving); | |
181 } | |
182 | |
183 public synchronized void expectIceGatheringChange( | |
184 IceGatheringState newState) { | |
185 expectedIceGatheringChanges.add(newState); | |
186 } | |
187 | |
188 @Override | |
189 public synchronized void onIceGatheringChange(IceGatheringState newState) { | |
190 // It's fine to get a variable number of GATHERING messages before | |
191 // COMPLETE fires (depending on how long the test runs) so we don't assert | |
192 // any particular count. | |
193 if (newState == IceGatheringState.GATHERING) { | |
194 return; | |
195 } | |
196 assertEquals(expectedIceGatheringChanges.removeFirst(), newState); | |
197 } | |
198 | |
199 public synchronized void expectAddStream(String label) { | |
200 expectedAddStreamLabels.add(label); | |
201 } | |
202 | |
203 @Override | |
204 public synchronized void onAddStream(MediaStream stream) { | |
205 assertEquals(expectedAddStreamLabels.removeFirst(), stream.label()); | |
206 assertEquals(1, stream.videoTracks.size()); | |
207 assertEquals(1, stream.audioTracks.size()); | |
208 assertTrue(stream.videoTracks.get(0).id().endsWith("VideoTrack")); | |
209 assertTrue(stream.audioTracks.get(0).id().endsWith("AudioTrack")); | |
210 assertEquals("video", stream.videoTracks.get(0).kind()); | |
211 assertEquals("audio", stream.audioTracks.get(0).kind()); | |
212 VideoRenderer renderer = createVideoRenderer(this); | |
213 stream.videoTracks.get(0).addRenderer(renderer); | |
214 assertNull(renderers.put( | |
215 stream, new WeakReference<VideoRenderer>(renderer))); | |
216 } | |
217 | |
218 public synchronized void expectRemoveStream(String label) { | |
219 expectedRemoveStreamLabels.add(label); | |
220 } | |
221 | |
222 @Override | |
223 public synchronized void onRemoveStream(MediaStream stream) { | |
224 assertEquals(expectedRemoveStreamLabels.removeFirst(), stream.label()); | |
225 WeakReference<VideoRenderer> renderer = renderers.remove(stream); | |
226 assertNotNull(renderer); | |
227 assertNotNull(renderer.get()); | |
228 assertEquals(1, stream.videoTracks.size()); | |
229 stream.videoTracks.get(0).removeRenderer(renderer.get()); | |
230 } | |
231 | |
232 public synchronized void expectDataChannel(String label) { | |
233 expectedRemoteDataChannelLabels.add(label); | |
234 } | |
235 | |
236 @Override | |
237 public synchronized void onDataChannel(DataChannel remoteDataChannel) { | |
238 assertEquals(expectedRemoteDataChannelLabels.removeFirst(), | |
239 remoteDataChannel.label()); | |
240 setDataChannel(remoteDataChannel); | |
241 assertEquals(DataChannel.State.CONNECTING, dataChannel.state()); | |
242 } | |
243 | |
244 public synchronized void expectRenegotiationNeeded() { | |
245 ++expectedRenegotiations; | |
246 } | |
247 | |
248 @Override | |
249 public synchronized void onRenegotiationNeeded() { | |
250 assertTrue(--expectedRenegotiations >= 0); | |
251 } | |
252 | |
253 public synchronized void expectMessage(ByteBuffer expectedBuffer, | |
254 boolean expectedBinary) { | |
255 expectedBuffers.add( | |
256 new DataChannel.Buffer(expectedBuffer, expectedBinary)); | |
257 } | |
258 | |
259 @Override | |
260 public synchronized void onMessage(DataChannel.Buffer buffer) { | |
261 DataChannel.Buffer expected = expectedBuffers.removeFirst(); | |
262 assertEquals(expected.binary, buffer.binary); | |
263 assertTrue(expected.data.equals(buffer.data)); | |
264 } | |
265 | |
266 @Override | |
267 public synchronized void onBufferedAmountChange(long previousAmount) { | |
268 assertFalse(previousAmount == dataChannel.bufferedAmount()); | |
269 } | |
270 | |
271 @Override | |
272 public synchronized void onStateChange() { | |
273 assertEquals(expectedStateChanges.removeFirst(), dataChannel.state()); | |
274 } | |
275 | |
276 public synchronized void expectStateChange(DataChannel.State state) { | |
277 expectedStateChanges.add(state); | |
278 } | |
279 | |
280 @Override | |
281 public synchronized void onComplete(StatsReport[] reports) { | |
282 if (--expectedStatsCallbacks < 0) { | |
283 throw new RuntimeException("Unexpected stats report: " + reports); | |
284 } | |
285 gotStatsReports.add(reports); | |
286 } | |
287 | |
288 public synchronized void expectStatsCallback() { | |
289 ++expectedStatsCallbacks; | |
290 } | |
291 | |
292 public synchronized LinkedList<StatsReport[]> takeStatsReports() { | |
293 LinkedList<StatsReport[]> got = gotStatsReports; | |
294 gotStatsReports = new LinkedList<StatsReport[]>(); | |
295 return got; | |
296 } | |
297 | |
298 // Return a set of expectations that haven't been satisfied yet, possibly | |
299 // empty if no such expectations exist. | |
300 public synchronized TreeSet<String> unsatisfiedExpectations() { | |
301 TreeSet<String> stillWaitingForExpectations = new TreeSet<String>(); | |
302 if (expectedIceCandidates > 0) { // See comment in onIceCandidate. | |
303 stillWaitingForExpectations.add("expectedIceCandidates"); | |
304 } | |
305 if (expectedErrors != 0) { | |
306 stillWaitingForExpectations.add("expectedErrors: " + expectedErrors); | |
307 } | |
308 if (expectedSignalingChanges.size() != 0) { | |
309 stillWaitingForExpectations.add( | |
310 "expectedSignalingChanges: " + expectedSignalingChanges.size()); | |
311 } | |
312 if (expectedIceConnectionChanges.size() != 0) { | |
313 stillWaitingForExpectations.add("expectedIceConnectionChanges: " + | |
314 expectedIceConnectionChanges.size()); | |
315 } | |
316 if (expectedIceGatheringChanges.size() != 0) { | |
317 stillWaitingForExpectations.add("expectedIceGatheringChanges: " + | |
318 expectedIceGatheringChanges.size()); | |
319 } | |
320 if (expectedAddStreamLabels.size() != 0) { | |
321 stillWaitingForExpectations.add( | |
322 "expectedAddStreamLabels: " + expectedAddStreamLabels.size()); | |
323 } | |
324 if (expectedRemoveStreamLabels.size() != 0) { | |
325 stillWaitingForExpectations.add( | |
326 "expectedRemoveStreamLabels: " + expectedRemoveStreamLabels.size()); | |
327 } | |
328 if (expectedFramesDelivered > 0) { | |
329 stillWaitingForExpectations.add( | |
330 "expectedFramesDelivered: " + expectedFramesDelivered); | |
331 } | |
332 if (!expectedBuffers.isEmpty()) { | |
333 stillWaitingForExpectations.add( | |
334 "expectedBuffers: " + expectedBuffers.size()); | |
335 } | |
336 if (!expectedStateChanges.isEmpty()) { | |
337 stillWaitingForExpectations.add( | |
338 "expectedStateChanges: " + expectedStateChanges.size()); | |
339 } | |
340 if (!expectedRemoteDataChannelLabels.isEmpty()) { | |
341 stillWaitingForExpectations.add("expectedRemoteDataChannelLabels: " + | |
342 expectedRemoteDataChannelLabels.size()); | |
343 } | |
344 if (expectedStatsCallbacks != 0) { | |
345 stillWaitingForExpectations.add( | |
346 "expectedStatsCallbacks: " + expectedStatsCallbacks); | |
347 } | |
348 return stillWaitingForExpectations; | |
349 } | |
350 | |
351 public boolean waitForAllExpectationsToBeSatisfied(int timeoutSeconds) { | |
352 // TODO(fischman): problems with this approach: | |
353 // - come up with something better than a poll loop | |
354 // - avoid serializing expectations explicitly; the test is not as robust | |
355 // as it could be because it must place expectations between wait | |
356 // statements very precisely (e.g. frame must not arrive before its | |
357 // expectation, and expectation must not be registered so early as to | |
358 // stall a wait). Use callbacks to fire off dependent steps instead of | |
359 // explicitly waiting, so there can be just a single wait at the end of | |
360 // the test. | |
361 long endTime = System.currentTimeMillis() + 1000 * timeoutSeconds; | |
362 TreeSet<String> prev = null; | |
363 TreeSet<String> stillWaitingForExpectations = unsatisfiedExpectations(); | |
364 while (!stillWaitingForExpectations.isEmpty()) { | |
365 if (!stillWaitingForExpectations.equals(prev)) { | |
366 System.out.println( | |
367 name + " still waiting at\n " + | |
368 (new Throwable()).getStackTrace()[1] + | |
369 "\n for: " + | |
370 Arrays.toString(stillWaitingForExpectations.toArray())); | |
371 } | |
372 if (endTime < System.currentTimeMillis()) { | |
373 System.out.println(name + " timed out waiting for: " | |
374 + Arrays.toString(stillWaitingForExpectations.toArray())); | |
375 return false; | |
376 } | |
377 try { | |
378 Thread.sleep(10); | |
379 } catch (InterruptedException e) { | |
380 throw new RuntimeException(e); | |
381 } | |
382 prev = stillWaitingForExpectations; | |
383 stillWaitingForExpectations = unsatisfiedExpectations(); | |
384 } | |
385 if (prev == null) { | |
386 System.out.println(name + " didn't need to wait at\n " + | |
387 (new Throwable()).getStackTrace()[1]); | |
388 } | |
389 return true; | |
390 } | |
391 | |
392 // This methods return a list of all currently gathered ice candidates or wa
its until | |
393 // 1 candidate have been gathered. | |
394 public List<IceCandidate> getAtLeastOneIceCandidate() throws InterruptedExce
ption { | |
395 synchronized (gotIceCandidates) { | |
396 while (gotIceCandidates.isEmpty()) { | |
397 gotIceCandidates.wait(); | |
398 } | |
399 return new LinkedList<IceCandidate>(gotIceCandidates); | |
400 } | |
401 } | |
402 } | |
403 | |
404 private static class SdpObserverLatch implements SdpObserver { | |
405 private boolean success = false; | |
406 private SessionDescription sdp = null; | |
407 private String error = null; | |
408 private CountDownLatch latch = new CountDownLatch(1); | |
409 | |
410 public SdpObserverLatch() {} | |
411 | |
412 @Override | |
413 public void onCreateSuccess(SessionDescription sdp) { | |
414 this.sdp = sdp; | |
415 onSetSuccess(); | |
416 } | |
417 | |
418 @Override | |
419 public void onSetSuccess() { | |
420 success = true; | |
421 latch.countDown(); | |
422 } | |
423 | |
424 @Override | |
425 public void onCreateFailure(String error) { | |
426 onSetFailure(error); | |
427 } | |
428 | |
429 @Override | |
430 public void onSetFailure(String error) { | |
431 this.error = error; | |
432 latch.countDown(); | |
433 } | |
434 | |
435 public boolean await() { | |
436 try { | |
437 assertTrue(latch.await(1000, TimeUnit.MILLISECONDS)); | |
438 return getSuccess(); | |
439 } catch (Exception e) { | |
440 throw new RuntimeException(e); | |
441 } | |
442 } | |
443 | |
444 public boolean getSuccess() { | |
445 return success; | |
446 } | |
447 | |
448 public SessionDescription getSdp() { | |
449 return sdp; | |
450 } | |
451 | |
452 public String getError() { | |
453 return error; | |
454 } | |
455 } | |
456 | |
457 static int videoWindowsMapped = -1; | |
458 | |
459 private static VideoRenderer createVideoRenderer( | |
460 VideoRenderer.Callbacks videoCallbacks) { | |
461 if (!RENDER_TO_GUI) { | |
462 return new VideoRenderer(videoCallbacks); | |
463 } | |
464 ++videoWindowsMapped; | |
465 assertTrue(videoWindowsMapped < 4); | |
466 int x = videoWindowsMapped % 2 != 0 ? 700 : 0; | |
467 int y = videoWindowsMapped >= 2 ? 0 : 500; | |
468 return VideoRenderer.createGui(x, y); | |
469 } | |
470 | |
471 // Return a weak reference to test that ownership is correctly held by | |
472 // PeerConnection, not by test code. | |
473 private static WeakReference<MediaStream> addTracksToPC( | |
474 PeerConnectionFactory factory, PeerConnection pc, | |
475 VideoSource videoSource, | |
476 String streamLabel, String videoTrackId, String audioTrackId, | |
477 VideoRenderer.Callbacks videoCallbacks) { | |
478 MediaStream lMS = factory.createLocalMediaStream(streamLabel); | |
479 VideoTrack videoTrack = | |
480 factory.createVideoTrack(videoTrackId, videoSource); | |
481 assertNotNull(videoTrack); | |
482 VideoRenderer videoRenderer = createVideoRenderer(videoCallbacks); | |
483 assertNotNull(videoRenderer); | |
484 videoTrack.addRenderer(videoRenderer); | |
485 lMS.addTrack(videoTrack); | |
486 // Just for fun, let's remove and re-add the track. | |
487 lMS.removeTrack(videoTrack); | |
488 lMS.addTrack(videoTrack); | |
489 lMS.addTrack(factory.createAudioTrack( | |
490 audioTrackId, factory.createAudioSource(new MediaConstraints()))); | |
491 pc.addStream(lMS); | |
492 return new WeakReference<MediaStream>(lMS); | |
493 } | |
494 | |
495 // Used for making sure thread handles are not leaked. | |
496 // Call initializeThreadCheck before a test and finalizeThreadCheck after | |
497 // a test. | |
498 void initializeThreadCheck() { | |
499 System.gc(); // Encourage any GC-related threads to start up. | |
500 threadsBeforeTest = allThreads(); | |
501 } | |
502 | |
503 void finalizeThreadCheck() throws Exception { | |
504 // TreeSet<String> threadsAfterTest = allThreads(); | |
505 | |
506 // TODO(tommi): Figure out a more reliable way to do this test. As is | |
507 // we're seeing three possible 'normal' situations: | |
508 // 1. before and after sets are equal. | |
509 // 2. before contains 3 threads that do not exist in after. | |
510 // 3. after contains 3 threads that do not exist in before. | |
511 // | |
512 // Maybe it would be better to do the thread enumeration from C++ and get | |
513 // the thread names as well, in order to determine what these 3 threads are. | |
514 | |
515 // assertEquals(threadsBeforeTest, threadsAfterTest); | |
516 // Thread.sleep(100); | |
517 } | |
518 | |
519 void doTest() throws Exception { | |
520 // Allow loopback interfaces too since our Android devices often don't | |
521 // have those. | |
522 PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); | |
523 options.networkIgnoreMask = 0; | |
524 PeerConnectionFactory factory = new PeerConnectionFactory(options); | |
525 // Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging. | |
526 // NOTE: this _must_ happen while |factory| is alive! | |
527 // Logging.enableTracing( | |
528 // "/tmp/PeerConnectionTest-log.txt", | |
529 // EnumSet.of(Logging.TraceLevel.TRACE_ALL), | |
530 // Logging.Severity.LS_SENSITIVE); | |
531 | |
532 MediaConstraints pcConstraints = new MediaConstraints(); | |
533 pcConstraints.mandatory.add( | |
534 new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")); | |
535 | |
536 LinkedList<PeerConnection.IceServer> iceServers = | |
537 new LinkedList<PeerConnection.IceServer>(); | |
538 iceServers.add(new PeerConnection.IceServer( | |
539 "stun:stun.l.google.com:19302")); | |
540 iceServers.add(new PeerConnection.IceServer( | |
541 "turn:fake.example.com", "fakeUsername", "fakePassword")); | |
542 ObserverExpectations offeringExpectations = | |
543 new ObserverExpectations("PCTest:offerer"); | |
544 PeerConnection offeringPC = factory.createPeerConnection( | |
545 iceServers, pcConstraints, offeringExpectations); | |
546 assertNotNull(offeringPC); | |
547 | |
548 ObserverExpectations answeringExpectations = | |
549 new ObserverExpectations("PCTest:answerer"); | |
550 PeerConnection answeringPC = factory.createPeerConnection( | |
551 iceServers, pcConstraints, answeringExpectations); | |
552 assertNotNull(answeringPC); | |
553 | |
554 // We want to use the same camera for offerer & answerer, so create it here | |
555 // instead of in addTracksToPC. | |
556 VideoSource videoSource = factory.createVideoSource( | |
557 VideoCapturer.create(""), new MediaConstraints()); | |
558 | |
559 offeringExpectations.expectRenegotiationNeeded(); | |
560 WeakReference<MediaStream> oLMS = addTracksToPC( | |
561 factory, offeringPC, videoSource, "offeredMediaStream", | |
562 "offeredVideoTrack", "offeredAudioTrack", offeringExpectations); | |
563 | |
564 offeringExpectations.expectRenegotiationNeeded(); | |
565 DataChannel offeringDC = offeringPC.createDataChannel( | |
566 "offeringDC", new DataChannel.Init()); | |
567 assertEquals("offeringDC", offeringDC.label()); | |
568 | |
569 offeringExpectations.setDataChannel(offeringDC); | |
570 SdpObserverLatch sdpLatch = new SdpObserverLatch(); | |
571 offeringPC.createOffer(sdpLatch, new MediaConstraints()); | |
572 assertTrue(sdpLatch.await()); | |
573 SessionDescription offerSdp = sdpLatch.getSdp(); | |
574 assertEquals(offerSdp.type, SessionDescription.Type.OFFER); | |
575 assertFalse(offerSdp.description.isEmpty()); | |
576 | |
577 sdpLatch = new SdpObserverLatch(); | |
578 answeringExpectations.expectSignalingChange( | |
579 SignalingState.HAVE_REMOTE_OFFER); | |
580 answeringExpectations.expectAddStream("offeredMediaStream"); | |
581 // SCTP DataChannels are announced via OPEN messages over the established | |
582 // connection (not via SDP), so answeringExpectations can only register | |
583 // expecting the channel during ICE, below. | |
584 answeringPC.setRemoteDescription(sdpLatch, offerSdp); | |
585 assertEquals( | |
586 PeerConnection.SignalingState.STABLE, offeringPC.signalingState()); | |
587 assertTrue(sdpLatch.await()); | |
588 assertNull(sdpLatch.getSdp()); | |
589 | |
590 answeringExpectations.expectRenegotiationNeeded(); | |
591 WeakReference<MediaStream> aLMS = addTracksToPC( | |
592 factory, answeringPC, videoSource, "answeredMediaStream", | |
593 "answeredVideoTrack", "answeredAudioTrack", answeringExpectations); | |
594 | |
595 sdpLatch = new SdpObserverLatch(); | |
596 answeringPC.createAnswer(sdpLatch, new MediaConstraints()); | |
597 assertTrue(sdpLatch.await()); | |
598 SessionDescription answerSdp = sdpLatch.getSdp(); | |
599 assertEquals(answerSdp.type, SessionDescription.Type.ANSWER); | |
600 assertFalse(answerSdp.description.isEmpty()); | |
601 | |
602 offeringExpectations.expectIceCandidates(2); | |
603 answeringExpectations.expectIceCandidates(2); | |
604 | |
605 offeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE); | |
606 answeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE); | |
607 | |
608 sdpLatch = new SdpObserverLatch(); | |
609 answeringExpectations.expectSignalingChange(SignalingState.STABLE); | |
610 answeringPC.setLocalDescription(sdpLatch, answerSdp); | |
611 assertTrue(sdpLatch.await()); | |
612 assertNull(sdpLatch.getSdp()); | |
613 | |
614 sdpLatch = new SdpObserverLatch(); | |
615 offeringExpectations.expectSignalingChange(SignalingState.HAVE_LOCAL_OFFER); | |
616 offeringPC.setLocalDescription(sdpLatch, offerSdp); | |
617 assertTrue(sdpLatch.await()); | |
618 assertNull(sdpLatch.getSdp()); | |
619 sdpLatch = new SdpObserverLatch(); | |
620 offeringExpectations.expectSignalingChange(SignalingState.STABLE); | |
621 offeringExpectations.expectAddStream("answeredMediaStream"); | |
622 | |
623 offeringExpectations.expectIceConnectionChange( | |
624 IceConnectionState.CHECKING); | |
625 offeringExpectations.expectIceConnectionChange( | |
626 IceConnectionState.CONNECTED); | |
627 // TODO(bemasc): uncomment once delivery of ICECompleted is reliable | |
628 // (https://code.google.com/p/webrtc/issues/detail?id=3021). | |
629 // | |
630 // offeringExpectations.expectIceConnectionChange( | |
631 // IceConnectionState.COMPLETED); | |
632 answeringExpectations.expectIceConnectionChange( | |
633 IceConnectionState.CHECKING); | |
634 answeringExpectations.expectIceConnectionChange( | |
635 IceConnectionState.CONNECTED); | |
636 | |
637 offeringPC.setRemoteDescription(sdpLatch, answerSdp); | |
638 assertTrue(sdpLatch.await()); | |
639 assertNull(sdpLatch.getSdp()); | |
640 | |
641 assertEquals(offeringPC.getLocalDescription().type, offerSdp.type); | |
642 assertEquals(offeringPC.getRemoteDescription().type, answerSdp.type); | |
643 assertEquals(answeringPC.getLocalDescription().type, answerSdp.type); | |
644 assertEquals(answeringPC.getRemoteDescription().type, offerSdp.type); | |
645 | |
646 assertEquals(offeringPC.getSenders().size(), 2); | |
647 assertEquals(offeringPC.getReceivers().size(), 2); | |
648 assertEquals(answeringPC.getSenders().size(), 2); | |
649 assertEquals(answeringPC.getReceivers().size(), 2); | |
650 | |
651 if (!RENDER_TO_GUI) { | |
652 // Wait for at least some frames to be delivered at each end (number | |
653 // chosen arbitrarily). | |
654 offeringExpectations.expectFramesDelivered(10); | |
655 answeringExpectations.expectFramesDelivered(10); | |
656 } | |
657 | |
658 offeringExpectations.expectStateChange(DataChannel.State.OPEN); | |
659 // See commentary about SCTP DataChannels above for why this is here. | |
660 answeringExpectations.expectDataChannel("offeringDC"); | |
661 answeringExpectations.expectStateChange(DataChannel.State.OPEN); | |
662 | |
663 // Wait for at least one ice candidate from the offering PC and forward them
to the answering | |
664 // PC. | |
665 for (IceCandidate candidate : offeringExpectations.getAtLeastOneIceCandidate
()) { | |
666 answeringPC.addIceCandidate(candidate); | |
667 } | |
668 | |
669 // Wait for at least one ice candidate from the answering PC and forward the
m to the offering | |
670 // PC. | |
671 for (IceCandidate candidate : answeringExpectations.getAtLeastOneIceCandidat
e()) { | |
672 offeringPC.addIceCandidate(candidate); | |
673 } | |
674 | |
675 assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_
SECONDS)); | |
676 assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT
_SECONDS)); | |
677 | |
678 assertEquals( | |
679 PeerConnection.SignalingState.STABLE, offeringPC.signalingState()); | |
680 assertEquals( | |
681 PeerConnection.SignalingState.STABLE, answeringPC.signalingState()); | |
682 | |
683 // Test send & receive UTF-8 text. | |
684 answeringExpectations.expectMessage( | |
685 ByteBuffer.wrap("hello!".getBytes(Charset.forName("UTF-8"))), false); | |
686 DataChannel.Buffer buffer = new DataChannel.Buffer( | |
687 ByteBuffer.wrap("hello!".getBytes(Charset.forName("UTF-8"))), false); | |
688 assertTrue(offeringExpectations.dataChannel.send(buffer)); | |
689 assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT
_SECONDS)); | |
690 | |
691 // Construct this binary message two different ways to ensure no | |
692 // shortcuts are taken. | |
693 ByteBuffer expectedBinaryMessage = ByteBuffer.allocateDirect(5); | |
694 for (byte i = 1; i < 6; ++i) { | |
695 expectedBinaryMessage.put(i); | |
696 } | |
697 expectedBinaryMessage.flip(); | |
698 offeringExpectations.expectMessage(expectedBinaryMessage, true); | |
699 assertTrue(answeringExpectations.dataChannel.send( | |
700 new DataChannel.Buffer( | |
701 ByteBuffer.wrap(new byte[] { 1, 2, 3, 4, 5 }), true))); | |
702 assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_
SECONDS)); | |
703 | |
704 offeringExpectations.expectStateChange(DataChannel.State.CLOSING); | |
705 answeringExpectations.expectStateChange(DataChannel.State.CLOSING); | |
706 offeringExpectations.expectStateChange(DataChannel.State.CLOSED); | |
707 answeringExpectations.expectStateChange(DataChannel.State.CLOSED); | |
708 answeringExpectations.dataChannel.close(); | |
709 offeringExpectations.dataChannel.close(); | |
710 | |
711 if (RENDER_TO_GUI) { | |
712 try { | |
713 Thread.sleep(3000); | |
714 } catch (Throwable t) { | |
715 throw new RuntimeException(t); | |
716 } | |
717 } | |
718 | |
719 // TODO(fischman) MOAR test ideas: | |
720 // - Test that PC.removeStream() works; requires a second | |
721 // createOffer/createAnswer dance. | |
722 // - audit each place that uses |constraints| for specifying non-trivial | |
723 // constraints (and ensure they're honored). | |
724 // - test error cases | |
725 // - ensure reasonable coverage of _jni.cc is achieved. Coverage is | |
726 // extra-important because of all the free-text (class/method names, etc) | |
727 // in JNI-style programming; make sure no typos! | |
728 // - Test that shutdown mid-interaction is crash-free. | |
729 | |
730 // Free the Java-land objects, collect them, and sleep a bit to make sure we | |
731 // don't get late-arrival crashes after the Java-land objects have been | |
732 // freed. | |
733 shutdownPC(offeringPC, offeringExpectations); | |
734 offeringPC = null; | |
735 shutdownPC(answeringPC, answeringExpectations); | |
736 answeringPC = null; | |
737 videoSource.dispose(); | |
738 factory.dispose(); | |
739 System.gc(); | |
740 } | |
741 | |
742 private static void shutdownPC( | |
743 PeerConnection pc, ObserverExpectations expectations) { | |
744 expectations.dataChannel.unregisterObserver(); | |
745 expectations.dataChannel.dispose(); | |
746 expectations.expectStatsCallback(); | |
747 assertTrue(pc.getStats(expectations, null)); | |
748 assertTrue(expectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)
); | |
749 expectations.expectIceConnectionChange(IceConnectionState.CLOSED); | |
750 expectations.expectSignalingChange(SignalingState.CLOSED); | |
751 pc.close(); | |
752 assertTrue(expectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)
); | |
753 expectations.expectStatsCallback(); | |
754 assertTrue(pc.getStats(expectations, null)); | |
755 assertTrue(expectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)
); | |
756 | |
757 System.out.println("FYI stats: "); | |
758 int reportIndex = -1; | |
759 for (StatsReport[] reports : expectations.takeStatsReports()) { | |
760 System.out.println(" Report #" + (++reportIndex)); | |
761 for (int i = 0; i < reports.length; ++i) { | |
762 System.out.println(" " + reports[i].toString()); | |
763 } | |
764 } | |
765 assertEquals(1, reportIndex); | |
766 System.out.println("End stats."); | |
767 | |
768 pc.dispose(); | |
769 } | |
770 | |
771 // Returns a set of thread IDs belonging to this process, as Strings. | |
772 private static TreeSet<String> allThreads() { | |
773 TreeSet<String> threads = new TreeSet<String>(); | |
774 // This pokes at /proc instead of using the Java APIs because we're also | |
775 // looking for libjingle/webrtc native threads, most of which won't have | |
776 // attached to the JVM. | |
777 for (String threadId : (new File("/proc/self/task")).list()) { | |
778 threads.add(threadId); | |
779 } | |
780 return threads; | |
781 } | |
782 } | |
OLD | NEW |