RTCStatsIntegrationTest added.

This is an integration test using peerconnectiontestwrapper.h to set up
and end to end test using a real PeerConnection implementation. These
tests will complement rtcstatscollector_unittest.cc which collects all
stats using mocks.

The integration test is set up so that all stats types are returned by
GetStats and verifies that expected dictionary members are defined. The
test could in the future be updated to include sanity checks for the
values of members. There is a sanity check that references to other
stats dictionaries yield existing stats of the appropriate type, but
other than that members are only tested for if they are defined not.

StatsCallback of rtcstatscollector_unittest.cc is moved so that it can
be reused and renamed to RTCStatsObtainer.

TODO: Audio stream track stats members are missing in the test. Find out
if this is because of a real problem or because of testing without real
devices. Do this before closing crbug.com/627816.

BUG=chromium:627816

Review-Url: https://codereview.webrtc.org/2521663002
Cr-Commit-Position: refs/heads/master@{#15287}
diff --git a/webrtc/api/BUILD.gn b/webrtc/api/BUILD.gn
index d827de0..fe1ade2 100644
--- a/webrtc/api/BUILD.gn
+++ b/webrtc/api/BUILD.gn
@@ -405,6 +405,7 @@
       "peerconnectionfactory_unittest.cc",
       "peerconnectioninterface_unittest.cc",
       "proxy_unittest.cc",
+      "rtcstats_integrationtest.cc",
       "rtcstatscollector_unittest.cc",
       "rtpsenderreceiver_unittest.cc",
       "sctputils_unittest.cc",
@@ -423,6 +424,7 @@
       "test/mockpeerconnectionobservers.h",
       "test/peerconnectiontestwrapper.cc",
       "test/peerconnectiontestwrapper.h",
+      "test/rtcstatsobtainer.h",
       "test/testsdpstrings.h",
       "videocapturertracksource_unittest.cc",
       "videotrack_unittest.cc",
diff --git a/webrtc/api/rtcstats_integrationtest.cc b/webrtc/api/rtcstats_integrationtest.cc
new file mode 100644
index 0000000..a74d19f
--- /dev/null
+++ b/webrtc/api/rtcstats_integrationtest.cc
@@ -0,0 +1,537 @@
+/*
+ *  Copyright 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <set>
+#include <vector>
+
+#include "webrtc/api/datachannelinterface.h"
+#include "webrtc/api/peerconnectioninterface.h"
+#include "webrtc/api/stats/rtcstats_objects.h"
+#include "webrtc/api/stats/rtcstatsreport.h"
+#include "webrtc/api/test/peerconnectiontestwrapper.h"
+#include "webrtc/api/test/rtcstatsobtainer.h"
+#include "webrtc/base/checks.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/physicalsocketserver.h"
+#include "webrtc/base/refcountedobject.h"
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/base/virtualsocketserver.h"
+
+namespace webrtc {
+
+namespace {
+
+const int64_t kGetStatsTimeoutMs = 10000;
+
+class RTCStatsIntegrationTest : public testing::Test {
+ public:
+  RTCStatsIntegrationTest()
+      : physical_socket_server_(),
+        virtual_socket_server_(&physical_socket_server_),
+        network_thread_(&virtual_socket_server_),
+        worker_thread_() {
+    RTC_CHECK(network_thread_.Start());
+    RTC_CHECK(worker_thread_.Start());
+
+    caller_ = new rtc::RefCountedObject<PeerConnectionTestWrapper>(
+        "caller", &network_thread_, &worker_thread_);
+    callee_ = new rtc::RefCountedObject<PeerConnectionTestWrapper>(
+        "callee", &network_thread_, &worker_thread_);
+  }
+
+  void StartCall() {
+    // Create PeerConnections and "connect" sigslots
+    PeerConnectionInterface::RTCConfiguration config;
+    PeerConnectionInterface::IceServer ice_server;
+    ice_server.uri = "stun:1.1.1.1:3478";
+    config.servers.push_back(ice_server);
+    EXPECT_TRUE(caller_->CreatePc(nullptr, config));
+    EXPECT_TRUE(callee_->CreatePc(nullptr, config));
+    PeerConnectionTestWrapper::Connect(caller_.get(), callee_.get());
+
+    // Get user media for audio and video
+    caller_->GetAndAddUserMedia(true, FakeConstraints(),
+                                true, FakeConstraints());
+    callee_->GetAndAddUserMedia(true, FakeConstraints(),
+                                true, FakeConstraints());
+
+    // Create data channels
+    DataChannelInit init;
+    caller_->CreateDataChannel("data", init);
+    callee_->CreateDataChannel("data", init);
+
+    // Negotiate and wait for call to establish
+    caller_->CreateOffer(nullptr);
+    caller_->WaitForCallEstablished();
+    callee_->WaitForCallEstablished();
+  }
+
+  rtc::scoped_refptr<const RTCStatsReport> GetStatsFromCaller() {
+    return GetStats(caller_->pc());
+  }
+
+  rtc::scoped_refptr<const RTCStatsReport> GetStatsFromCallee() {
+    return GetStats(callee_->pc());
+  }
+
+ protected:
+  static rtc::scoped_refptr<const RTCStatsReport> GetStats(
+      PeerConnectionInterface* pc) {
+    rtc::scoped_refptr<RTCStatsObtainer> stats_obtainer =
+        RTCStatsObtainer::Create();
+    pc->GetStats(stats_obtainer);
+    EXPECT_TRUE_WAIT(stats_obtainer->report(), kGetStatsTimeoutMs);
+    return stats_obtainer->report();
+  }
+
+  // These objects use each other and must be constructed/destroyed in this
+  // order. Relationship:
+  // |physical_socket_server_| <- |virtual_socket_server_| <- |network_thread_|
+  rtc::PhysicalSocketServer physical_socket_server_;
+  rtc::VirtualSocketServer virtual_socket_server_;
+  rtc::Thread network_thread_;
+  rtc::Thread worker_thread_;
+  rtc::scoped_refptr<PeerConnectionTestWrapper> caller_;
+  rtc::scoped_refptr<PeerConnectionTestWrapper> callee_;
+};
+
+class RTCStatsVerifier {
+ public:
+  RTCStatsVerifier(const RTCStatsReport* report, const RTCStats* stats)
+      : report_(report), stats_(stats), all_tests_successful_(true) {
+    RTC_CHECK(report_);
+    RTC_CHECK(stats_);
+    for (const RTCStatsMemberInterface* member : stats_->Members()) {
+      untested_members_.insert(member);
+    }
+  }
+
+  void MarkMemberTested(
+      const RTCStatsMemberInterface& member, bool test_successful) {
+    untested_members_.erase(&member);
+    all_tests_successful_ &= test_successful;
+  }
+
+  void TestMemberIsDefined(const RTCStatsMemberInterface& member) {
+    EXPECT_TRUE(member.is_defined()) <<
+      stats_->type() << "." << member.name() << "[" << stats_->id() <<
+      "] was undefined.";
+    MarkMemberTested(member, member.is_defined());
+  }
+
+  void TestMemberIsUndefined(const RTCStatsMemberInterface& member) {
+    EXPECT_FALSE(member.is_defined()) <<
+      stats_->type() << "." << member.name() << "[" << stats_->id() <<
+      "] was defined (" << member.ValueToString() << ").";
+    MarkMemberTested(member, !member.is_defined());
+  }
+
+  void TestMemberIsIDReference(
+      const RTCStatsMemberInterface& member,
+      const char* expected_type) {
+    TestMemberIsIDReference(member, expected_type, false);
+  }
+
+  void TestMemberIsOptionalIDReference(
+      const RTCStatsMemberInterface& member,
+      const char* expected_type) {
+    TestMemberIsIDReference(member, expected_type, true);
+  }
+
+  bool ExpectAllMembersSuccessfullyTested() {
+    if (untested_members_.empty())
+      return all_tests_successful_;
+    for (const RTCStatsMemberInterface* member : untested_members_) {
+      EXPECT_TRUE(false) <<
+          stats_->type() << "." << member->name() << "[" << stats_->id() <<
+          "] was not tested.";
+    }
+    return false;
+  }
+
+ private:
+  void TestMemberIsIDReference(
+      const RTCStatsMemberInterface& member,
+      const char* expected_type,
+      bool optional) {
+    if (optional && !member.is_defined()) {
+      MarkMemberTested(member, true);
+      return;
+    }
+    bool valid_reference = false;
+    if (member.is_defined()) {
+      if (member.type() == RTCStatsMemberInterface::kString) {
+        // A single ID.
+        const RTCStatsMember<std::string>& id =
+            member.cast_to<RTCStatsMember<std::string>>();
+        const RTCStats* referenced_stats = report_->Get(*id);
+        valid_reference =
+            referenced_stats && referenced_stats->type() == expected_type;
+      } else if (member.type() == RTCStatsMemberInterface::kSequenceString) {
+        // A vector of IDs.
+        valid_reference = true;
+        const RTCStatsMember<std::vector<std::string>>& ids =
+            member.cast_to<RTCStatsMember<std::vector<std::string>>>();
+        for (const std::string id : *ids) {
+          const RTCStats* referenced_stats = report_->Get(id);
+          if (!referenced_stats || referenced_stats->type() != expected_type) {
+            valid_reference = false;
+            break;
+          }
+        }
+      }
+    }
+    EXPECT_TRUE(valid_reference) <<
+      stats_->type() << "." << member.name() << " is not a reference to an " <<
+      "existing dictionary of type " << expected_type << " (" <<
+      member.ValueToString() << ").";
+    MarkMemberTested(member, valid_reference);
+  }
+
+  rtc::scoped_refptr<const RTCStatsReport> report_;
+  const RTCStats* stats_;
+  std::set<const RTCStatsMemberInterface*> untested_members_;
+  bool all_tests_successful_;
+};
+
+class RTCStatsReportVerifier {
+ public:
+  static std::set<const char*> StatsTypes() {
+    std::set<const char*> stats_types;
+    stats_types.insert(RTCCertificateStats::kType);
+    stats_types.insert(RTCCodecStats::kType);
+    stats_types.insert(RTCDataChannelStats::kType);
+    stats_types.insert(RTCIceCandidatePairStats::kType);
+    stats_types.insert(RTCLocalIceCandidateStats::kType);
+    stats_types.insert(RTCRemoteIceCandidateStats::kType);
+    stats_types.insert(RTCMediaStreamStats::kType);
+    stats_types.insert(RTCMediaStreamTrackStats::kType);
+    stats_types.insert(RTCPeerConnectionStats::kType);
+    stats_types.insert(RTCInboundRTPStreamStats::kType);
+    stats_types.insert(RTCOutboundRTPStreamStats::kType);
+    stats_types.insert(RTCTransportStats::kType);
+    return stats_types;
+  }
+
+  explicit RTCStatsReportVerifier(const RTCStatsReport* report)
+      : report_(report) {
+  }
+
+  void VerifyReport() {
+    std::set<const char*> missing_stats = StatsTypes();
+    bool verify_successful = true;
+    for (const RTCStats& stats : *report_) {
+      missing_stats.erase(stats.type());
+      if (stats.type() == RTCCertificateStats::kType) {
+        verify_successful &= VerifyRTCCertificateStats(
+            stats.cast_to<RTCCertificateStats>());
+      } else if (stats.type() == RTCCodecStats::kType) {
+        verify_successful &= VerifyRTCCodecStats(
+            stats.cast_to<RTCCodecStats>());
+      } else if (stats.type() == RTCDataChannelStats::kType) {
+        verify_successful &= VerifyRTCDataChannelStats(
+            stats.cast_to<RTCDataChannelStats>());
+      } else if (stats.type() == RTCIceCandidatePairStats::kType) {
+        verify_successful &= VerifyRTCIceCandidatePairStats(
+            stats.cast_to<RTCIceCandidatePairStats>());
+      } else if (stats.type() == RTCLocalIceCandidateStats::kType) {
+        verify_successful &= VerifyRTCLocalIceCandidateStats(
+            stats.cast_to<RTCLocalIceCandidateStats>());
+      } else if (stats.type() == RTCRemoteIceCandidateStats::kType) {
+        verify_successful &= VerifyRTCRemoteIceCandidateStats(
+            stats.cast_to<RTCRemoteIceCandidateStats>());
+      } else if (stats.type() == RTCMediaStreamStats::kType) {
+        verify_successful &= VerifyRTCMediaStreamStats(
+            stats.cast_to<RTCMediaStreamStats>());
+      } else if (stats.type() == RTCMediaStreamTrackStats::kType) {
+        verify_successful &= VerifyRTCMediaStreamTrackStats(
+            stats.cast_to<RTCMediaStreamTrackStats>());
+      } else if (stats.type() == RTCPeerConnectionStats::kType) {
+        verify_successful &= VerifyRTCPeerConnectionStats(
+            stats.cast_to<RTCPeerConnectionStats>());
+      } else if (stats.type() == RTCInboundRTPStreamStats::kType) {
+        verify_successful &= VerifyRTCInboundRTPStreamStats(
+            stats.cast_to<RTCInboundRTPStreamStats>());
+      } else if (stats.type() == RTCOutboundRTPStreamStats::kType) {
+        verify_successful &= VerifyRTCOutboundRTPStreamStats(
+            stats.cast_to<RTCOutboundRTPStreamStats>());
+      } else if (stats.type() == RTCTransportStats::kType) {
+        verify_successful &= VerifyRTCTransportStats(
+            stats.cast_to<RTCTransportStats>());
+      } else {
+        EXPECT_TRUE(false) << "Unrecognized stats type: " << stats.type();
+        verify_successful = false;
+      }
+    }
+    if (!missing_stats.empty()) {
+      verify_successful = false;
+      for (const char* missing : missing_stats) {
+        EXPECT_TRUE(false) << "Missing expected stats type: " << missing;
+      }
+    }
+    EXPECT_TRUE(verify_successful) <<
+        "One or more problems with the stats. This is the report:\n" <<
+        report_->ToString();
+  }
+
+  bool VerifyRTCCertificateStats(
+      const RTCCertificateStats& certificate) {
+    RTCStatsVerifier verifier(report_, &certificate);
+    verifier.TestMemberIsDefined(certificate.fingerprint);
+    verifier.TestMemberIsDefined(certificate.fingerprint_algorithm);
+    verifier.TestMemberIsDefined(certificate.base64_certificate);
+    verifier.TestMemberIsOptionalIDReference(
+        certificate.issuer_certificate_id, RTCCertificateStats::kType);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCCodecStats(
+      const RTCCodecStats& codec) {
+    RTCStatsVerifier verifier(report_, &codec);
+    verifier.TestMemberIsDefined(codec.payload_type);
+    verifier.TestMemberIsDefined(codec.codec);
+    verifier.TestMemberIsDefined(codec.clock_rate);
+    verifier.TestMemberIsUndefined(codec.channels);
+    verifier.TestMemberIsUndefined(codec.parameters);
+    verifier.TestMemberIsUndefined(codec.implementation);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCDataChannelStats(
+      const RTCDataChannelStats& data_channel) {
+    RTCStatsVerifier verifier(report_, &data_channel);
+    verifier.TestMemberIsDefined(data_channel.label);
+    verifier.TestMemberIsDefined(data_channel.protocol);
+    verifier.TestMemberIsDefined(data_channel.datachannelid);
+    verifier.TestMemberIsDefined(data_channel.state);
+    verifier.TestMemberIsDefined(data_channel.messages_sent);
+    verifier.TestMemberIsDefined(data_channel.bytes_sent);
+    verifier.TestMemberIsDefined(data_channel.messages_received);
+    verifier.TestMemberIsDefined(data_channel.bytes_received);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCIceCandidatePairStats(
+      const RTCIceCandidatePairStats& candidate_pair) {
+    RTCStatsVerifier verifier(report_, &candidate_pair);
+    verifier.TestMemberIsUndefined(candidate_pair.transport_id);
+    verifier.TestMemberIsIDReference(
+        candidate_pair.local_candidate_id, RTCLocalIceCandidateStats::kType);
+    verifier.TestMemberIsIDReference(
+        candidate_pair.remote_candidate_id, RTCRemoteIceCandidateStats::kType);
+    verifier.TestMemberIsUndefined(candidate_pair.state);
+    verifier.TestMemberIsUndefined(candidate_pair.priority);
+    verifier.TestMemberIsUndefined(candidate_pair.nominated);
+    verifier.TestMemberIsDefined(candidate_pair.writable);
+    verifier.TestMemberIsUndefined(candidate_pair.readable);
+    verifier.TestMemberIsDefined(candidate_pair.bytes_sent);
+    verifier.TestMemberIsDefined(candidate_pair.bytes_received);
+    verifier.TestMemberIsUndefined(candidate_pair.total_rtt);
+    verifier.TestMemberIsDefined(candidate_pair.current_rtt);
+    verifier.TestMemberIsUndefined(candidate_pair.available_outgoing_bitrate);
+    verifier.TestMemberIsUndefined(candidate_pair.available_incoming_bitrate);
+    verifier.TestMemberIsUndefined(candidate_pair.requests_received);
+    verifier.TestMemberIsDefined(candidate_pair.requests_sent);
+    verifier.TestMemberIsDefined(candidate_pair.responses_received);
+    verifier.TestMemberIsDefined(candidate_pair.responses_sent);
+    verifier.TestMemberIsUndefined(candidate_pair.retransmissions_received);
+    verifier.TestMemberIsUndefined(candidate_pair.retransmissions_sent);
+    verifier.TestMemberIsUndefined(candidate_pair.consent_requests_received);
+    verifier.TestMemberIsUndefined(candidate_pair.consent_requests_sent);
+    verifier.TestMemberIsUndefined(candidate_pair.consent_responses_received);
+    verifier.TestMemberIsUndefined(candidate_pair.consent_responses_sent);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCIceCandidateStats(
+      const RTCIceCandidateStats& candidate) {
+    RTCStatsVerifier verifier(report_, &candidate);
+    verifier.TestMemberIsDefined(candidate.ip);
+    verifier.TestMemberIsDefined(candidate.port);
+    verifier.TestMemberIsDefined(candidate.protocol);
+    verifier.TestMemberIsDefined(candidate.candidate_type);
+    verifier.TestMemberIsDefined(candidate.priority);
+    verifier.TestMemberIsUndefined(candidate.url);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCLocalIceCandidateStats(
+      const RTCLocalIceCandidateStats& local_candidate) {
+    return VerifyRTCIceCandidateStats(local_candidate);
+  }
+
+  bool VerifyRTCRemoteIceCandidateStats(
+      const RTCRemoteIceCandidateStats& remote_candidate) {
+    return VerifyRTCIceCandidateStats(remote_candidate);
+  }
+
+  bool VerifyRTCMediaStreamStats(
+      const RTCMediaStreamStats& media_stream) {
+    RTCStatsVerifier verifier(report_, &media_stream);
+    verifier.TestMemberIsDefined(media_stream.stream_identifier);
+    verifier.TestMemberIsIDReference(
+        media_stream.track_ids, RTCMediaStreamTrackStats::kType);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCMediaStreamTrackStats(
+      const RTCMediaStreamTrackStats& media_stream_track) {
+    RTCStatsVerifier verifier(report_, &media_stream_track);
+    verifier.TestMemberIsDefined(media_stream_track.track_identifier);
+    verifier.TestMemberIsDefined(media_stream_track.remote_source);
+    verifier.TestMemberIsDefined(media_stream_track.ended);
+    verifier.TestMemberIsDefined(media_stream_track.detached);
+    verifier.TestMemberIsUndefined(media_stream_track.ssrc_ids);
+    // Video or audio media stream track?
+    if (media_stream_track.frame_width.is_defined()) {
+      // Video-only members
+      verifier.TestMemberIsDefined(media_stream_track.frame_width);
+      verifier.TestMemberIsDefined(media_stream_track.frame_height);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_per_second);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_sent);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_received);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_decoded);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_dropped);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_corrupted);
+      verifier.TestMemberIsUndefined(media_stream_track.partial_frames_lost);
+      verifier.TestMemberIsUndefined(media_stream_track.full_frames_lost);
+      // Audio-only members should be undefined
+      verifier.TestMemberIsUndefined(media_stream_track.audio_level);
+      verifier.TestMemberIsUndefined(media_stream_track.echo_return_loss);
+      verifier.TestMemberIsUndefined(
+          media_stream_track.echo_return_loss_enhancement);
+    } else {
+      // Video-only members should be undefined
+      verifier.TestMemberIsUndefined(media_stream_track.frame_width);
+      verifier.TestMemberIsUndefined(media_stream_track.frame_height);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_per_second);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_sent);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_received);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_decoded);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_dropped);
+      verifier.TestMemberIsUndefined(media_stream_track.frames_corrupted);
+      verifier.TestMemberIsUndefined(media_stream_track.partial_frames_lost);
+      verifier.TestMemberIsUndefined(media_stream_track.full_frames_lost);
+      // Audio-only members
+      // TODO(hbos): Why are the audio track missing |audio_level|,
+      // |echo_return_loss| and |echo_return_loss_enhancement|? Is this a real
+      // problem or does it have to do with testing and not using real devices?
+      // crbug.com/627816
+      verifier.MarkMemberTested(media_stream_track.audio_level, true);
+      verifier.MarkMemberTested(media_stream_track.echo_return_loss, true);
+      verifier.MarkMemberTested(
+          media_stream_track.echo_return_loss_enhancement, true);
+    }
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCPeerConnectionStats(
+      const RTCPeerConnectionStats& peer_connection) {
+    RTCStatsVerifier verifier(report_, &peer_connection);
+    verifier.TestMemberIsDefined(peer_connection.data_channels_opened);
+    verifier.TestMemberIsDefined(peer_connection.data_channels_closed);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  void VerifyRTCRTPStreamStats(
+      const RTCRTPStreamStats& stream, RTCStatsVerifier* verifier) {
+    verifier->TestMemberIsDefined(stream.ssrc);
+    verifier->TestMemberIsUndefined(stream.associate_stats_id);
+    verifier->TestMemberIsDefined(stream.is_remote);
+    verifier->TestMemberIsDefined(stream.media_type);
+    verifier->TestMemberIsUndefined(stream.media_track_id);
+    verifier->TestMemberIsIDReference(
+        stream.transport_id, RTCTransportStats::kType);
+    verifier->TestMemberIsDefined(stream.codec_id);
+    if (stream.media_type.is_defined() && *stream.media_type == "video") {
+      verifier->TestMemberIsDefined(stream.fir_count);
+      verifier->TestMemberIsDefined(stream.pli_count);
+      verifier->TestMemberIsDefined(stream.nack_count);
+    } else {
+      verifier->TestMemberIsUndefined(stream.fir_count);
+      verifier->TestMemberIsUndefined(stream.pli_count);
+      verifier->TestMemberIsUndefined(stream.nack_count);
+    }
+    verifier->TestMemberIsUndefined(stream.sli_count);
+  }
+
+  bool VerifyRTCInboundRTPStreamStats(
+      const RTCInboundRTPStreamStats& inbound_stream) {
+    RTCStatsVerifier verifier(report_, &inbound_stream);
+    VerifyRTCRTPStreamStats(inbound_stream, &verifier);
+    verifier.TestMemberIsDefined(inbound_stream.packets_received);
+    verifier.TestMemberIsDefined(inbound_stream.bytes_received);
+    verifier.TestMemberIsUndefined(inbound_stream.packets_lost);
+    if (inbound_stream.media_type.is_defined() &&
+        *inbound_stream.media_type == "video") {
+      verifier.TestMemberIsUndefined(inbound_stream.jitter);
+    } else {
+      verifier.TestMemberIsDefined(inbound_stream.jitter);
+    }
+    verifier.TestMemberIsDefined(inbound_stream.fraction_lost);
+    verifier.TestMemberIsUndefined(inbound_stream.packets_discarded);
+    verifier.TestMemberIsUndefined(inbound_stream.packets_repaired);
+    verifier.TestMemberIsUndefined(inbound_stream.burst_packets_lost);
+    verifier.TestMemberIsUndefined(inbound_stream.burst_packets_discarded);
+    verifier.TestMemberIsUndefined(inbound_stream.burst_loss_count);
+    verifier.TestMemberIsUndefined(inbound_stream.burst_discard_count);
+    verifier.TestMemberIsUndefined(inbound_stream.burst_loss_rate);
+    verifier.TestMemberIsUndefined(inbound_stream.burst_discard_rate);
+    verifier.TestMemberIsUndefined(inbound_stream.gap_loss_rate);
+    verifier.TestMemberIsUndefined(inbound_stream.gap_discard_rate);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCOutboundRTPStreamStats(
+      const RTCOutboundRTPStreamStats& outbound_stream) {
+    RTCStatsVerifier verifier(report_, &outbound_stream);
+    VerifyRTCRTPStreamStats(outbound_stream, &verifier);
+    verifier.TestMemberIsDefined(outbound_stream.packets_sent);
+    verifier.TestMemberIsDefined(outbound_stream.bytes_sent);
+    verifier.TestMemberIsUndefined(outbound_stream.target_bitrate);
+    verifier.TestMemberIsDefined(outbound_stream.round_trip_time);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCTransportStats(
+      const RTCTransportStats& transport) {
+    RTCStatsVerifier verifier(report_, &transport);
+    verifier.TestMemberIsDefined(transport.bytes_sent);
+    verifier.TestMemberIsDefined(transport.bytes_received);
+    verifier.TestMemberIsOptionalIDReference(
+        transport.rtcp_transport_stats_id, RTCTransportStats::kType);
+    verifier.TestMemberIsDefined(transport.active_connection);
+    verifier.TestMemberIsDefined(transport.selected_candidate_pair_id);
+    verifier.TestMemberIsDefined(transport.local_certificate_id);
+    verifier.TestMemberIsDefined(transport.remote_certificate_id);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+ private:
+  rtc::scoped_refptr<const RTCStatsReport> report_;
+};
+
+TEST_F(RTCStatsIntegrationTest, GetStatsFromCaller) {
+  StartCall();
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsFromCaller();
+  RTCStatsReportVerifier(report.get()).VerifyReport();
+}
+
+TEST_F(RTCStatsIntegrationTest, GetStatsFromCallee) {
+  StartCall();
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsFromCallee();
+  RTCStatsReportVerifier(report.get()).VerifyReport();
+}
+
+}  // namespace
+
+}  // namespace webrtc
diff --git a/webrtc/api/rtcstatscollector_unittest.cc b/webrtc/api/rtcstatscollector_unittest.cc
index f27eb32..e4c3118 100644
--- a/webrtc/api/rtcstatscollector_unittest.cc
+++ b/webrtc/api/rtcstatscollector_unittest.cc
@@ -24,6 +24,7 @@
 #include "webrtc/api/test/mock_datachannel.h"
 #include "webrtc/api/test/mock_peerconnection.h"
 #include "webrtc/api/test/mock_webrtcsession.h"
+#include "webrtc/api/test/rtcstatsobtainer.h"
 #include "webrtc/base/checks.h"
 #include "webrtc/base/fakeclock.h"
 #include "webrtc/base/fakesslidentity.h"
@@ -472,37 +473,6 @@
   int produced_on_network_thread_ = 0;
 };
 
-class StatsCallback : public RTCStatsCollectorCallback {
- public:
-  static rtc::scoped_refptr<StatsCallback> Create(
-      rtc::scoped_refptr<const RTCStatsReport>* report_ptr = nullptr) {
-    return rtc::scoped_refptr<StatsCallback>(
-        new rtc::RefCountedObject<StatsCallback>(report_ptr));
-  }
-
-  void OnStatsDelivered(
-      const rtc::scoped_refptr<const RTCStatsReport>& report) override {
-    EXPECT_TRUE(thread_checker_.CalledOnValidThread());
-    report_ = report;
-    if (report_ptr_)
-      *report_ptr_ = report_;
-  }
-
-  rtc::scoped_refptr<const RTCStatsReport> report() const {
-    EXPECT_TRUE(thread_checker_.CalledOnValidThread());
-    return report_;
-  }
-
- protected:
-  explicit StatsCallback(rtc::scoped_refptr<const RTCStatsReport>* report_ptr)
-      : report_ptr_(report_ptr) {}
-
- private:
-  rtc::ThreadChecker thread_checker_;
-  rtc::scoped_refptr<const RTCStatsReport> report_;
-  rtc::scoped_refptr<const RTCStatsReport>* report_ptr_;
-};
-
 class RTCStatsCollectorTest : public testing::Test {
  public:
   RTCStatsCollectorTest()
@@ -512,7 +482,7 @@
   }
 
   rtc::scoped_refptr<const RTCStatsReport> GetStatsReport() {
-    rtc::scoped_refptr<StatsCallback> callback = StatsCallback::Create();
+    rtc::scoped_refptr<RTCStatsObtainer> callback = RTCStatsObtainer::Create();
     collector_->GetStatsReport(callback);
     EXPECT_TRUE_WAIT(callback->report(), kGetStatsReportTimeoutMs);
     int64_t after = rtc::TimeUTCMicros();
@@ -723,7 +693,7 @@
 
 TEST_F(RTCStatsCollectorTest, SingleCallback) {
   rtc::scoped_refptr<const RTCStatsReport> result;
-  collector_->GetStatsReport(StatsCallback::Create(&result));
+  collector_->GetStatsReport(RTCStatsObtainer::Create(&result));
   EXPECT_TRUE_WAIT(result, kGetStatsReportTimeoutMs);
 }
 
@@ -731,9 +701,9 @@
   rtc::scoped_refptr<const RTCStatsReport> a;
   rtc::scoped_refptr<const RTCStatsReport> b;
   rtc::scoped_refptr<const RTCStatsReport> c;
-  collector_->GetStatsReport(StatsCallback::Create(&a));
-  collector_->GetStatsReport(StatsCallback::Create(&b));
-  collector_->GetStatsReport(StatsCallback::Create(&c));
+  collector_->GetStatsReport(RTCStatsObtainer::Create(&a));
+  collector_->GetStatsReport(RTCStatsObtainer::Create(&b));
+  collector_->GetStatsReport(RTCStatsObtainer::Create(&c));
   EXPECT_TRUE_WAIT(a, kGetStatsReportTimeoutMs);
   EXPECT_TRUE_WAIT(b, kGetStatsReportTimeoutMs);
   EXPECT_TRUE_WAIT(c, kGetStatsReportTimeoutMs);
@@ -761,11 +731,11 @@
   rtc::scoped_refptr<const RTCStatsReport> a;
   rtc::scoped_refptr<const RTCStatsReport> b;
   rtc::scoped_refptr<const RTCStatsReport> c;
-  collector_->GetStatsReport(StatsCallback::Create(&a));
-  collector_->GetStatsReport(StatsCallback::Create(&b));
+  collector_->GetStatsReport(RTCStatsObtainer::Create(&a));
+  collector_->GetStatsReport(RTCStatsObtainer::Create(&b));
   // Cache is invalidated after 50 ms.
   test_->fake_clock().AdvanceTime(rtc::TimeDelta::FromMilliseconds(51));
-  collector_->GetStatsReport(StatsCallback::Create(&c));
+  collector_->GetStatsReport(RTCStatsObtainer::Create(&c));
   EXPECT_TRUE_WAIT(a, kGetStatsReportTimeoutMs);
   EXPECT_TRUE_WAIT(b, kGetStatsReportTimeoutMs);
   EXPECT_TRUE_WAIT(c, kGetStatsReportTimeoutMs);
diff --git a/webrtc/api/test/peerconnectiontestwrapper.h b/webrtc/api/test/peerconnectiontestwrapper.h
index 3cdac49..6433e8f 100644
--- a/webrtc/api/test/peerconnectiontestwrapper.h
+++ b/webrtc/api/test/peerconnectiontestwrapper.h
@@ -43,6 +43,8 @@
       const webrtc::MediaConstraintsInterface* constraints,
       const webrtc::PeerConnectionInterface::RTCConfiguration& config);
 
+  webrtc::PeerConnectionInterface* pc() { return peer_connection_.get(); }
+
   rtc::scoped_refptr<webrtc::DataChannelInterface> CreateDataChannel(
       const std::string& label,
       const webrtc::DataChannelInit& init);
diff --git a/webrtc/api/test/rtcstatsobtainer.h b/webrtc/api/test/rtcstatsobtainer.h
new file mode 100644
index 0000000..aeae87c
--- /dev/null
+++ b/webrtc/api/test/rtcstatsobtainer.h
@@ -0,0 +1,53 @@
+/*
+ *  Copyright 2016 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_API_TEST_RTCSTATSOBTAINER_H_
+#define WEBRTC_API_TEST_RTCSTATSOBTAINER_H_
+
+#include "webrtc/api/stats/rtcstatsreport.h"
+#include "webrtc/base/gunit.h"
+
+namespace webrtc {
+
+class RTCStatsObtainer : public RTCStatsCollectorCallback {
+ public:
+  static rtc::scoped_refptr<RTCStatsObtainer> Create(
+      rtc::scoped_refptr<const RTCStatsReport>* report_ptr = nullptr) {
+    return rtc::scoped_refptr<RTCStatsObtainer>(
+        new rtc::RefCountedObject<RTCStatsObtainer>(report_ptr));
+  }
+
+  void OnStatsDelivered(
+      const rtc::scoped_refptr<const RTCStatsReport>& report) override {
+    EXPECT_TRUE(thread_checker_.CalledOnValidThread());
+    report_ = report;
+    if (report_ptr_)
+      *report_ptr_ = report_;
+  }
+
+  rtc::scoped_refptr<const RTCStatsReport> report() const {
+    EXPECT_TRUE(thread_checker_.CalledOnValidThread());
+    return report_;
+  }
+
+ protected:
+  explicit RTCStatsObtainer(
+      rtc::scoped_refptr<const RTCStatsReport>* report_ptr)
+      : report_ptr_(report_ptr) {}
+
+ private:
+  rtc::ThreadChecker thread_checker_;
+  rtc::scoped_refptr<const RTCStatsReport> report_;
+  rtc::scoped_refptr<const RTCStatsReport>* report_ptr_;
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_API_TEST_RTCSTATSOBTAINER_H_