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