• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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