Home | History | Annotate | Download | only in webrtc
      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