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 ObserverExpectations(String name)92 public ObserverExpectations(String name) { 93 this.name = name; 94 } 95 setDataChannel(DataChannel dataChannel)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 expectIceCandidates(int count)103 public synchronized void expectIceCandidates(int count) { 104 expectedIceCandidates += count; 105 } 106 107 @Override onIceCandidate(IceCandidate candidate)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 setSize(int width, int height)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 expectFramesDelivered(int count)135 public synchronized void expectFramesDelivered(int count) { 136 assertFalse(RENDER_TO_GUI); 137 expectedFramesDelivered += count; 138 } 139 140 @Override renderFrame(VideoRenderer.I420Frame frame)141 public synchronized void renderFrame(VideoRenderer.I420Frame frame) { 142 setSize(frame.rotatedWidth(), frame.rotatedHeight()); 143 --expectedFramesDelivered; 144 VideoRenderer.renderFrameDone(frame); 145 } 146 expectSignalingChange(SignalingState newState)147 public synchronized void expectSignalingChange(SignalingState newState) { 148 expectedSignalingChanges.add(newState); 149 } 150 151 @Override onSignalingChange(SignalingState newState)152 public synchronized void onSignalingChange(SignalingState newState) { 153 assertEquals(expectedSignalingChanges.removeFirst(), newState); 154 } 155 expectIceConnectionChange( IceConnectionState newState)156 public synchronized void expectIceConnectionChange( 157 IceConnectionState newState) { 158 expectedIceConnectionChanges.add(newState); 159 } 160 161 @Override onIceConnectionChange( IceConnectionState newState)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 " + newState); 172 return; 173 } 174 175 assertEquals(expectedIceConnectionChanges.removeFirst(), newState); 176 } 177 178 @Override onIceConnectionReceivingChange(boolean receiving)179 public synchronized void onIceConnectionReceivingChange(boolean receiving) { 180 System.out.println(name + "Got an ice connection receiving change " + receiving); 181 } 182 expectIceGatheringChange( IceGatheringState newState)183 public synchronized void expectIceGatheringChange( 184 IceGatheringState newState) { 185 expectedIceGatheringChanges.add(newState); 186 } 187 188 @Override onIceGatheringChange(IceGatheringState newState)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 expectAddStream(String label)199 public synchronized void expectAddStream(String label) { 200 expectedAddStreamLabels.add(label); 201 } 202 203 @Override onAddStream(MediaStream stream)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 expectRemoveStream(String label)218 public synchronized void expectRemoveStream(String label) { 219 expectedRemoveStreamLabels.add(label); 220 } 221 222 @Override onRemoveStream(MediaStream stream)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 expectDataChannel(String label)232 public synchronized void expectDataChannel(String label) { 233 expectedRemoteDataChannelLabels.add(label); 234 } 235 236 @Override onDataChannel(DataChannel remoteDataChannel)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 expectRenegotiationNeeded()244 public synchronized void expectRenegotiationNeeded() { 245 ++expectedRenegotiations; 246 } 247 248 @Override onRenegotiationNeeded()249 public synchronized void onRenegotiationNeeded() { 250 assertTrue(--expectedRenegotiations >= 0); 251 } 252 expectMessage(ByteBuffer expectedBuffer, boolean expectedBinary)253 public synchronized void expectMessage(ByteBuffer expectedBuffer, 254 boolean expectedBinary) { 255 expectedBuffers.add( 256 new DataChannel.Buffer(expectedBuffer, expectedBinary)); 257 } 258 259 @Override onMessage(DataChannel.Buffer buffer)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 onBufferedAmountChange(long previousAmount)267 public synchronized void onBufferedAmountChange(long previousAmount) { 268 assertFalse(previousAmount == dataChannel.bufferedAmount()); 269 } 270 271 @Override onStateChange()272 public synchronized void onStateChange() { 273 assertEquals(expectedStateChanges.removeFirst(), dataChannel.state()); 274 } 275 expectStateChange(DataChannel.State state)276 public synchronized void expectStateChange(DataChannel.State state) { 277 expectedStateChanges.add(state); 278 } 279 280 @Override onComplete(StatsReport[] reports)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 expectStatsCallback()288 public synchronized void expectStatsCallback() { 289 ++expectedStatsCallbacks; 290 } 291 takeStatsReports()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. unsatisfiedExpectations()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 waitForAllExpectationsToBeSatisfied(int timeoutSeconds)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 waits until 393 // 1 candidate have been gathered. getAtLeastOneIceCandidate()394 public List<IceCandidate> getAtLeastOneIceCandidate() throws InterruptedException { 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 SdpObserverLatch()410 public SdpObserverLatch() {} 411 412 @Override onCreateSuccess(SessionDescription sdp)413 public void onCreateSuccess(SessionDescription sdp) { 414 this.sdp = sdp; 415 onSetSuccess(); 416 } 417 418 @Override onSetSuccess()419 public void onSetSuccess() { 420 success = true; 421 latch.countDown(); 422 } 423 424 @Override onCreateFailure(String error)425 public void onCreateFailure(String error) { 426 onSetFailure(error); 427 } 428 429 @Override onSetFailure(String error)430 public void onSetFailure(String error) { 431 this.error = error; 432 latch.countDown(); 433 } 434 await()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 getSuccess()444 public boolean getSuccess() { 445 return success; 446 } 447 getSdp()448 public SessionDescription getSdp() { 449 return sdp; 450 } 451 getError()452 public String getError() { 453 return error; 454 } 455 } 456 457 static int videoWindowsMapped = -1; 458 createVideoRenderer( VideoRenderer.Callbacks videoCallbacks)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 PeerConnectionFactory factory = new PeerConnectionFactory(); 521 // Uncomment to get ALL WebRTC tracing and SENSITIVE libjingle logging. 522 // NOTE: this _must_ happen while |factory| is alive! 523 // Logging.enableTracing( 524 // "/tmp/PeerConnectionTest-log.txt", 525 // EnumSet.of(Logging.TraceLevel.TRACE_ALL), 526 // Logging.Severity.LS_SENSITIVE); 527 528 // Allow loopback interfaces too since our Android devices often don't 529 // have those. 530 PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); 531 options.networkIgnoreMask = 0; 532 factory.setOptions(options); 533 534 MediaConstraints pcConstraints = new MediaConstraints(); 535 pcConstraints.mandatory.add( 536 new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true")); 537 538 LinkedList<PeerConnection.IceServer> iceServers = 539 new LinkedList<PeerConnection.IceServer>(); 540 iceServers.add(new PeerConnection.IceServer( 541 "stun:stun.l.google.com:19302")); 542 iceServers.add(new PeerConnection.IceServer( 543 "turn:fake.example.com", "fakeUsername", "fakePassword")); 544 ObserverExpectations offeringExpectations = 545 new ObserverExpectations("PCTest:offerer"); 546 PeerConnection offeringPC = factory.createPeerConnection( 547 iceServers, pcConstraints, offeringExpectations); 548 assertNotNull(offeringPC); 549 550 ObserverExpectations answeringExpectations = 551 new ObserverExpectations("PCTest:answerer"); 552 PeerConnection answeringPC = factory.createPeerConnection( 553 iceServers, pcConstraints, answeringExpectations); 554 assertNotNull(answeringPC); 555 556 // We want to use the same camera for offerer & answerer, so create it here 557 // instead of in addTracksToPC. 558 VideoSource videoSource = factory.createVideoSource( 559 VideoCapturer.create(""), new MediaConstraints()); 560 561 offeringExpectations.expectRenegotiationNeeded(); 562 WeakReference<MediaStream> oLMS = addTracksToPC( 563 factory, offeringPC, videoSource, "offeredMediaStream", 564 "offeredVideoTrack", "offeredAudioTrack", offeringExpectations); 565 566 offeringExpectations.expectRenegotiationNeeded(); 567 DataChannel offeringDC = offeringPC.createDataChannel( 568 "offeringDC", new DataChannel.Init()); 569 assertEquals("offeringDC", offeringDC.label()); 570 571 offeringExpectations.setDataChannel(offeringDC); 572 SdpObserverLatch sdpLatch = new SdpObserverLatch(); 573 offeringPC.createOffer(sdpLatch, new MediaConstraints()); 574 assertTrue(sdpLatch.await()); 575 SessionDescription offerSdp = sdpLatch.getSdp(); 576 assertEquals(offerSdp.type, SessionDescription.Type.OFFER); 577 assertFalse(offerSdp.description.isEmpty()); 578 579 sdpLatch = new SdpObserverLatch(); 580 answeringExpectations.expectSignalingChange( 581 SignalingState.HAVE_REMOTE_OFFER); 582 answeringExpectations.expectAddStream("offeredMediaStream"); 583 // SCTP DataChannels are announced via OPEN messages over the established 584 // connection (not via SDP), so answeringExpectations can only register 585 // expecting the channel during ICE, below. 586 answeringPC.setRemoteDescription(sdpLatch, offerSdp); 587 assertEquals( 588 PeerConnection.SignalingState.STABLE, offeringPC.signalingState()); 589 assertTrue(sdpLatch.await()); 590 assertNull(sdpLatch.getSdp()); 591 592 answeringExpectations.expectRenegotiationNeeded(); 593 WeakReference<MediaStream> aLMS = addTracksToPC( 594 factory, answeringPC, videoSource, "answeredMediaStream", 595 "answeredVideoTrack", "answeredAudioTrack", answeringExpectations); 596 597 sdpLatch = new SdpObserverLatch(); 598 answeringPC.createAnswer(sdpLatch, new MediaConstraints()); 599 assertTrue(sdpLatch.await()); 600 SessionDescription answerSdp = sdpLatch.getSdp(); 601 assertEquals(answerSdp.type, SessionDescription.Type.ANSWER); 602 assertFalse(answerSdp.description.isEmpty()); 603 604 offeringExpectations.expectIceCandidates(2); 605 answeringExpectations.expectIceCandidates(2); 606 607 offeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE); 608 answeringExpectations.expectIceGatheringChange(IceGatheringState.COMPLETE); 609 610 sdpLatch = new SdpObserverLatch(); 611 answeringExpectations.expectSignalingChange(SignalingState.STABLE); 612 answeringPC.setLocalDescription(sdpLatch, answerSdp); 613 assertTrue(sdpLatch.await()); 614 assertNull(sdpLatch.getSdp()); 615 616 sdpLatch = new SdpObserverLatch(); 617 offeringExpectations.expectSignalingChange(SignalingState.HAVE_LOCAL_OFFER); 618 offeringPC.setLocalDescription(sdpLatch, offerSdp); 619 assertTrue(sdpLatch.await()); 620 assertNull(sdpLatch.getSdp()); 621 sdpLatch = new SdpObserverLatch(); 622 offeringExpectations.expectSignalingChange(SignalingState.STABLE); 623 offeringExpectations.expectAddStream("answeredMediaStream"); 624 625 offeringExpectations.expectIceConnectionChange( 626 IceConnectionState.CHECKING); 627 offeringExpectations.expectIceConnectionChange( 628 IceConnectionState.CONNECTED); 629 // TODO(bemasc): uncomment once delivery of ICECompleted is reliable 630 // (https://code.google.com/p/webrtc/issues/detail?id=3021). 631 // 632 // offeringExpectations.expectIceConnectionChange( 633 // IceConnectionState.COMPLETED); 634 answeringExpectations.expectIceConnectionChange( 635 IceConnectionState.CHECKING); 636 answeringExpectations.expectIceConnectionChange( 637 IceConnectionState.CONNECTED); 638 639 offeringPC.setRemoteDescription(sdpLatch, answerSdp); 640 assertTrue(sdpLatch.await()); 641 assertNull(sdpLatch.getSdp()); 642 643 assertEquals(offeringPC.getLocalDescription().type, offerSdp.type); 644 assertEquals(offeringPC.getRemoteDescription().type, answerSdp.type); 645 assertEquals(answeringPC.getLocalDescription().type, answerSdp.type); 646 assertEquals(answeringPC.getRemoteDescription().type, offerSdp.type); 647 648 assertEquals(offeringPC.getSenders().size(), 2); 649 assertEquals(offeringPC.getReceivers().size(), 2); 650 assertEquals(answeringPC.getSenders().size(), 2); 651 assertEquals(answeringPC.getReceivers().size(), 2); 652 653 if (!RENDER_TO_GUI) { 654 // Wait for at least some frames to be delivered at each end (number 655 // chosen arbitrarily). 656 offeringExpectations.expectFramesDelivered(10); 657 answeringExpectations.expectFramesDelivered(10); 658 } 659 660 offeringExpectations.expectStateChange(DataChannel.State.OPEN); 661 // See commentary about SCTP DataChannels above for why this is here. 662 answeringExpectations.expectDataChannel("offeringDC"); 663 answeringExpectations.expectStateChange(DataChannel.State.OPEN); 664 665 // Wait for at least one ice candidate from the offering PC and forward them to the answering 666 // PC. 667 for (IceCandidate candidate : offeringExpectations.getAtLeastOneIceCandidate()) { 668 answeringPC.addIceCandidate(candidate); 669 } 670 671 // Wait for at least one ice candidate from the answering PC and forward them to the offering 672 // PC. 673 for (IceCandidate candidate : answeringExpectations.getAtLeastOneIceCandidate()) { 674 offeringPC.addIceCandidate(candidate); 675 } 676 677 assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)); 678 assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)); 679 680 assertEquals( 681 PeerConnection.SignalingState.STABLE, offeringPC.signalingState()); 682 assertEquals( 683 PeerConnection.SignalingState.STABLE, answeringPC.signalingState()); 684 685 // Test send & receive UTF-8 text. 686 answeringExpectations.expectMessage( 687 ByteBuffer.wrap("hello!".getBytes(Charset.forName("UTF-8"))), false); 688 DataChannel.Buffer buffer = new DataChannel.Buffer( 689 ByteBuffer.wrap("hello!".getBytes(Charset.forName("UTF-8"))), false); 690 assertTrue(offeringExpectations.dataChannel.send(buffer)); 691 assertTrue(answeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)); 692 693 // Construct this binary message two different ways to ensure no 694 // shortcuts are taken. 695 ByteBuffer expectedBinaryMessage = ByteBuffer.allocateDirect(5); 696 for (byte i = 1; i < 6; ++i) { 697 expectedBinaryMessage.put(i); 698 } 699 expectedBinaryMessage.flip(); 700 offeringExpectations.expectMessage(expectedBinaryMessage, true); 701 assertTrue(answeringExpectations.dataChannel.send( 702 new DataChannel.Buffer( 703 ByteBuffer.wrap(new byte[] { 1, 2, 3, 4, 5 }), true))); 704 assertTrue(offeringExpectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)); 705 706 offeringExpectations.expectStateChange(DataChannel.State.CLOSING); 707 answeringExpectations.expectStateChange(DataChannel.State.CLOSING); 708 offeringExpectations.expectStateChange(DataChannel.State.CLOSED); 709 answeringExpectations.expectStateChange(DataChannel.State.CLOSED); 710 answeringExpectations.dataChannel.close(); 711 offeringExpectations.dataChannel.close(); 712 713 if (RENDER_TO_GUI) { 714 try { 715 Thread.sleep(3000); 716 } catch (Throwable t) { 717 throw new RuntimeException(t); 718 } 719 } 720 721 // TODO(fischman) MOAR test ideas: 722 // - Test that PC.removeStream() works; requires a second 723 // createOffer/createAnswer dance. 724 // - audit each place that uses |constraints| for specifying non-trivial 725 // constraints (and ensure they're honored). 726 // - test error cases 727 // - ensure reasonable coverage of _jni.cc is achieved. Coverage is 728 // extra-important because of all the free-text (class/method names, etc) 729 // in JNI-style programming; make sure no typos! 730 // - Test that shutdown mid-interaction is crash-free. 731 732 // Free the Java-land objects, collect them, and sleep a bit to make sure we 733 // don't get late-arrival crashes after the Java-land objects have been 734 // freed. 735 shutdownPC(offeringPC, offeringExpectations); 736 offeringPC = null; 737 shutdownPC(answeringPC, answeringExpectations); 738 answeringPC = null; 739 videoSource.dispose(); 740 factory.dispose(); 741 System.gc(); 742 } 743 744 private static void shutdownPC( 745 PeerConnection pc, ObserverExpectations expectations) { 746 expectations.dataChannel.unregisterObserver(); 747 expectations.dataChannel.dispose(); 748 expectations.expectStatsCallback(); 749 assertTrue(pc.getStats(expectations, null)); 750 assertTrue(expectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)); 751 expectations.expectIceConnectionChange(IceConnectionState.CLOSED); 752 expectations.expectSignalingChange(SignalingState.CLOSED); 753 pc.close(); 754 assertTrue(expectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)); 755 expectations.expectStatsCallback(); 756 assertTrue(pc.getStats(expectations, null)); 757 assertTrue(expectations.waitForAllExpectationsToBeSatisfied(TIMEOUT_SECONDS)); 758 759 System.out.println("FYI stats: "); 760 int reportIndex = -1; 761 for (StatsReport[] reports : expectations.takeStatsReports()) { 762 System.out.println(" Report #" + (++reportIndex)); 763 for (int i = 0; i < reports.length; ++i) { 764 System.out.println(" " + reports[i].toString()); 765 } 766 } 767 assertEquals(1, reportIndex); 768 System.out.println("End stats."); 769 770 pc.dispose(); 771 } 772 773 // Returns a set of thread IDs belonging to this process, as Strings. 774 private static TreeSet<String> allThreads() { 775 TreeSet<String> threads = new TreeSet<String>(); 776 // This pokes at /proc instead of using the Java APIs because we're also 777 // looking for libjingle/webrtc native threads, most of which won't have 778 // attached to the JVM. 779 for (String threadId : (new File("/proc/self/task")).list()) { 780 threads.add(threadId); 781 } 782 return threads; 783 } 784 } 785