Home | History | Annotate | Download | only in base
      1 /*
      2  *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #ifndef WEBRTC_BASE_TESTUTILS_H__
     12 #define WEBRTC_BASE_TESTUTILS_H__
     13 
     14 // Utilities for testing rtc infrastructure in unittests
     15 
     16 #if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
     17 #include <X11/Xlib.h>
     18 #include <X11/extensions/Xrandr.h>
     19 
     20 // X defines a few macros that stomp on types that gunit.h uses.
     21 #undef None
     22 #undef Bool
     23 #endif
     24 
     25 #include <map>
     26 #include <vector>
     27 #include "webrtc/base/asyncsocket.h"
     28 #include "webrtc/base/common.h"
     29 #include "webrtc/base/gunit.h"
     30 #include "webrtc/base/nethelpers.h"
     31 #include "webrtc/base/pathutils.h"
     32 #include "webrtc/base/stream.h"
     33 #include "webrtc/base/stringencode.h"
     34 #include "webrtc/base/stringutils.h"
     35 #include "webrtc/base/thread.h"
     36 
     37 namespace testing {
     38 
     39 using namespace rtc;
     40 
     41 ///////////////////////////////////////////////////////////////////////////////
     42 // StreamSink - Monitor asynchronously signalled events from StreamInterface
     43 // or AsyncSocket (which should probably be a StreamInterface.
     44 ///////////////////////////////////////////////////////////////////////////////
     45 
     46 // Note: Any event that is an error is treaded as SSE_ERROR instead of that
     47 // event.
     48 
     49 enum StreamSinkEvent {
     50   SSE_OPEN  = SE_OPEN,
     51   SSE_READ  = SE_READ,
     52   SSE_WRITE = SE_WRITE,
     53   SSE_CLOSE = SE_CLOSE,
     54   SSE_ERROR = 16
     55 };
     56 
     57 class StreamSink : public sigslot::has_slots<> {
     58  public:
     59   void Monitor(StreamInterface* stream) {
     60    stream->SignalEvent.connect(this, &StreamSink::OnEvent);
     61    events_.erase(stream);
     62   }
     63   void Unmonitor(StreamInterface* stream) {
     64    stream->SignalEvent.disconnect(this);
     65    // In case you forgot to unmonitor a previous object with this address
     66    events_.erase(stream);
     67   }
     68   bool Check(StreamInterface* stream, StreamSinkEvent event, bool reset = true) {
     69     return DoCheck(stream, event, reset);
     70   }
     71   int Events(StreamInterface* stream, bool reset = true) {
     72     return DoEvents(stream, reset);
     73   }
     74 
     75   void Monitor(AsyncSocket* socket) {
     76    socket->SignalConnectEvent.connect(this, &StreamSink::OnConnectEvent);
     77    socket->SignalReadEvent.connect(this, &StreamSink::OnReadEvent);
     78    socket->SignalWriteEvent.connect(this, &StreamSink::OnWriteEvent);
     79    socket->SignalCloseEvent.connect(this, &StreamSink::OnCloseEvent);
     80    // In case you forgot to unmonitor a previous object with this address
     81    events_.erase(socket);
     82   }
     83   void Unmonitor(AsyncSocket* socket) {
     84    socket->SignalConnectEvent.disconnect(this);
     85    socket->SignalReadEvent.disconnect(this);
     86    socket->SignalWriteEvent.disconnect(this);
     87    socket->SignalCloseEvent.disconnect(this);
     88    events_.erase(socket);
     89   }
     90   bool Check(AsyncSocket* socket, StreamSinkEvent event, bool reset = true) {
     91     return DoCheck(socket, event, reset);
     92   }
     93   int Events(AsyncSocket* socket, bool reset = true) {
     94     return DoEvents(socket, reset);
     95   }
     96 
     97  private:
     98   typedef std::map<void*,int> EventMap;
     99 
    100   void OnEvent(StreamInterface* stream, int events, int error) {
    101     if (error) {
    102       events = SSE_ERROR;
    103     }
    104     AddEvents(stream, events);
    105   }
    106   void OnConnectEvent(AsyncSocket* socket) {
    107     AddEvents(socket, SSE_OPEN);
    108   }
    109   void OnReadEvent(AsyncSocket* socket) {
    110     AddEvents(socket, SSE_READ);
    111   }
    112   void OnWriteEvent(AsyncSocket* socket) {
    113     AddEvents(socket, SSE_WRITE);
    114   }
    115   void OnCloseEvent(AsyncSocket* socket, int error) {
    116     AddEvents(socket, (0 == error) ? SSE_CLOSE : SSE_ERROR);
    117   }
    118 
    119   void AddEvents(void* obj, int events) {
    120     EventMap::iterator it = events_.find(obj);
    121     if (events_.end() == it) {
    122       events_.insert(EventMap::value_type(obj, events));
    123     } else {
    124       it->second |= events;
    125     }
    126   }
    127   bool DoCheck(void* obj, StreamSinkEvent event, bool reset) {
    128     EventMap::iterator it = events_.find(obj);
    129     if ((events_.end() == it) || (0 == (it->second & event))) {
    130       return false;
    131     }
    132     if (reset) {
    133       it->second &= ~event;
    134     }
    135     return true;
    136   }
    137   int DoEvents(void* obj, bool reset) {
    138     EventMap::iterator it = events_.find(obj);
    139     if (events_.end() == it)
    140       return 0;
    141     int events = it->second;
    142     if (reset) {
    143       it->second = 0;
    144     }
    145     return events;
    146   }
    147 
    148   EventMap events_;
    149 };
    150 
    151 ///////////////////////////////////////////////////////////////////////////////
    152 // StreamSource - Implements stream interface and simulates asynchronous
    153 // events on the stream, without a network.  Also buffers written data.
    154 ///////////////////////////////////////////////////////////////////////////////
    155 
    156 class StreamSource : public StreamInterface {
    157 public:
    158   StreamSource() {
    159     Clear();
    160   }
    161 
    162   void Clear() {
    163     readable_data_.clear();
    164     written_data_.clear();
    165     state_ = SS_CLOSED;
    166     read_block_ = 0;
    167     write_block_ = SIZE_UNKNOWN;
    168   }
    169   void QueueString(const char* data) {
    170     QueueData(data, strlen(data));
    171   }
    172   void QueueStringF(const char* format, ...) {
    173     va_list args;
    174     va_start(args, format);
    175     char buffer[1024];
    176     size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
    177     ASSERT(len < sizeof(buffer) - 1);
    178     va_end(args);
    179     QueueData(buffer, len);
    180   }
    181   void QueueData(const char* data, size_t len) {
    182     readable_data_.insert(readable_data_.end(), data, data + len);
    183     if ((SS_OPEN == state_) && (readable_data_.size() == len)) {
    184       SignalEvent(this, SE_READ, 0);
    185     }
    186   }
    187   std::string ReadData() {
    188     std::string data;
    189     // avoid accessing written_data_[0] if it is undefined
    190     if (written_data_.size() > 0) {
    191       data.insert(0, &written_data_[0], written_data_.size());
    192     }
    193     written_data_.clear();
    194     return data;
    195   }
    196   void SetState(StreamState state) {
    197     int events = 0;
    198     if ((SS_OPENING == state_) && (SS_OPEN == state)) {
    199       events |= SE_OPEN;
    200       if (!readable_data_.empty()) {
    201         events |= SE_READ;
    202       }
    203     } else if ((SS_CLOSED != state_) && (SS_CLOSED == state)) {
    204       events |= SE_CLOSE;
    205     }
    206     state_ = state;
    207     if (events) {
    208       SignalEvent(this, events, 0);
    209     }
    210   }
    211   // Will cause Read to block when there are pos bytes in the read queue.
    212   void SetReadBlock(size_t pos) { read_block_ = pos; }
    213   // Will cause Write to block when there are pos bytes in the write queue.
    214   void SetWriteBlock(size_t pos) { write_block_ = pos; }
    215 
    216   virtual StreamState GetState() const { return state_; }
    217   virtual StreamResult Read(void* buffer, size_t buffer_len,
    218                             size_t* read, int* error) {
    219     if (SS_CLOSED == state_) {
    220       if (error) *error = -1;
    221       return SR_ERROR;
    222     }
    223     if ((SS_OPENING == state_) || (readable_data_.size() <= read_block_)) {
    224       return SR_BLOCK;
    225     }
    226     size_t count = _min(buffer_len, readable_data_.size() - read_block_);
    227     memcpy(buffer, &readable_data_[0], count);
    228     size_t new_size = readable_data_.size() - count;
    229     // Avoid undefined access beyond the last element of the vector.
    230     // This only happens when new_size is 0.
    231     if (count < readable_data_.size()) {
    232       memmove(&readable_data_[0], &readable_data_[count], new_size);
    233     }
    234     readable_data_.resize(new_size);
    235     if (read) *read = count;
    236     return SR_SUCCESS;
    237   }
    238   virtual StreamResult Write(const void* data, size_t data_len,
    239                              size_t* written, int* error) {
    240     if (SS_CLOSED == state_) {
    241       if (error) *error = -1;
    242       return SR_ERROR;
    243     }
    244     if (SS_OPENING == state_) {
    245       return SR_BLOCK;
    246     }
    247     if (SIZE_UNKNOWN != write_block_) {
    248       if (written_data_.size() >= write_block_) {
    249         return SR_BLOCK;
    250       }
    251       if (data_len > (write_block_ - written_data_.size())) {
    252         data_len = write_block_ - written_data_.size();
    253       }
    254     }
    255     if (written) *written = data_len;
    256     const char* cdata = static_cast<const char*>(data);
    257     written_data_.insert(written_data_.end(), cdata, cdata + data_len);
    258     return SR_SUCCESS;
    259   }
    260   virtual void Close() { state_ = SS_CLOSED; }
    261 
    262 private:
    263   typedef std::vector<char> Buffer;
    264   Buffer readable_data_, written_data_;
    265   StreamState state_;
    266   size_t read_block_, write_block_;
    267 };
    268 
    269 ///////////////////////////////////////////////////////////////////////////////
    270 // SocketTestClient
    271 // Creates a simulated client for testing.  Works on real and virtual networks.
    272 ///////////////////////////////////////////////////////////////////////////////
    273 
    274 class SocketTestClient : public sigslot::has_slots<> {
    275 public:
    276   SocketTestClient() {
    277     Init(NULL, AF_INET);
    278   }
    279   SocketTestClient(AsyncSocket* socket) {
    280     Init(socket, socket->GetLocalAddress().family());
    281   }
    282   SocketTestClient(const SocketAddress& address) {
    283     Init(NULL, address.family());
    284     socket_->Connect(address);
    285   }
    286 
    287   AsyncSocket* socket() { return socket_.get(); }
    288 
    289   void QueueString(const char* data) {
    290     QueueData(data, strlen(data));
    291   }
    292   void QueueStringF(const char* format, ...) {
    293     va_list args;
    294     va_start(args, format);
    295     char buffer[1024];
    296     size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
    297     ASSERT(len < sizeof(buffer) - 1);
    298     va_end(args);
    299     QueueData(buffer, len);
    300   }
    301   void QueueData(const char* data, size_t len) {
    302     send_buffer_.insert(send_buffer_.end(), data, data + len);
    303     if (Socket::CS_CONNECTED == socket_->GetState()) {
    304       Flush();
    305     }
    306   }
    307   std::string ReadData() {
    308     std::string data(&recv_buffer_[0], recv_buffer_.size());
    309     recv_buffer_.clear();
    310     return data;
    311   }
    312 
    313   bool IsConnected() const {
    314     return (Socket::CS_CONNECTED == socket_->GetState());
    315   }
    316   bool IsClosed() const {
    317     return (Socket::CS_CLOSED == socket_->GetState());
    318   }
    319 
    320 private:
    321   typedef std::vector<char> Buffer;
    322 
    323   void Init(AsyncSocket* socket, int family) {
    324     if (!socket) {
    325       socket = Thread::Current()->socketserver()
    326           ->CreateAsyncSocket(family, SOCK_STREAM);
    327     }
    328     socket_.reset(socket);
    329     socket_->SignalConnectEvent.connect(this,
    330       &SocketTestClient::OnConnectEvent);
    331     socket_->SignalReadEvent.connect(this, &SocketTestClient::OnReadEvent);
    332     socket_->SignalWriteEvent.connect(this, &SocketTestClient::OnWriteEvent);
    333     socket_->SignalCloseEvent.connect(this, &SocketTestClient::OnCloseEvent);
    334   }
    335 
    336   void Flush() {
    337     size_t sent = 0;
    338     while (sent < send_buffer_.size()) {
    339       int result = socket_->Send(&send_buffer_[sent],
    340                                  send_buffer_.size() - sent);
    341       if (result > 0) {
    342         sent += result;
    343       } else {
    344         break;
    345       }
    346     }
    347     size_t new_size = send_buffer_.size() - sent;
    348     memmove(&send_buffer_[0], &send_buffer_[sent], new_size);
    349     send_buffer_.resize(new_size);
    350   }
    351 
    352   void OnConnectEvent(AsyncSocket* socket) {
    353     if (!send_buffer_.empty()) {
    354       Flush();
    355     }
    356   }
    357   void OnReadEvent(AsyncSocket* socket) {
    358     char data[64 * 1024];
    359     int result = socket_->Recv(data, ARRAY_SIZE(data));
    360     if (result > 0) {
    361       recv_buffer_.insert(recv_buffer_.end(), data, data + result);
    362     }
    363   }
    364   void OnWriteEvent(AsyncSocket* socket) {
    365     if (!send_buffer_.empty()) {
    366       Flush();
    367     }
    368   }
    369   void OnCloseEvent(AsyncSocket* socket, int error) {
    370   }
    371 
    372   scoped_ptr<AsyncSocket> socket_;
    373   Buffer send_buffer_, recv_buffer_;
    374 };
    375 
    376 ///////////////////////////////////////////////////////////////////////////////
    377 // SocketTestServer
    378 // Creates a simulated server for testing.  Works on real and virtual networks.
    379 ///////////////////////////////////////////////////////////////////////////////
    380 
    381 class SocketTestServer : public sigslot::has_slots<> {
    382  public:
    383   SocketTestServer(const SocketAddress& address)
    384       : socket_(Thread::Current()->socketserver()
    385                 ->CreateAsyncSocket(address.family(), SOCK_STREAM))
    386   {
    387     socket_->SignalReadEvent.connect(this, &SocketTestServer::OnReadEvent);
    388     socket_->Bind(address);
    389     socket_->Listen(5);
    390   }
    391   virtual ~SocketTestServer() {
    392     clear();
    393   }
    394 
    395   size_t size() const { return clients_.size(); }
    396   SocketTestClient* client(size_t index) const { return clients_[index]; }
    397   SocketTestClient* operator[](size_t index) const { return client(index); }
    398 
    399   void clear() {
    400     for (size_t i=0; i<clients_.size(); ++i) {
    401       delete clients_[i];
    402     }
    403     clients_.clear();
    404   }
    405 
    406  private:
    407   void OnReadEvent(AsyncSocket* socket) {
    408     AsyncSocket* accepted =
    409       static_cast<AsyncSocket*>(socket_->Accept(NULL));
    410     if (!accepted)
    411       return;
    412     clients_.push_back(new SocketTestClient(accepted));
    413   }
    414 
    415   scoped_ptr<AsyncSocket> socket_;
    416   std::vector<SocketTestClient*> clients_;
    417 };
    418 
    419 ///////////////////////////////////////////////////////////////////////////////
    420 // Generic Utilities
    421 ///////////////////////////////////////////////////////////////////////////////
    422 
    423 inline bool ReadFile(const char* filename, std::string* contents) {
    424   FILE* fp = fopen(filename, "rb");
    425   if (!fp)
    426     return false;
    427   char buffer[1024*64];
    428   size_t read;
    429   contents->clear();
    430   while ((read = fread(buffer, 1, sizeof(buffer), fp))) {
    431     contents->append(buffer, read);
    432   }
    433   bool success = (0 != feof(fp));
    434   fclose(fp);
    435   return success;
    436 }
    437 
    438 // Look in parent dir for parallel directory.
    439 inline rtc::Pathname GetSiblingDirectory(
    440     const std::string& parallel_dir) {
    441   rtc::Pathname path = rtc::Filesystem::GetCurrentDirectory();
    442   while (!path.empty()) {
    443     rtc::Pathname potential_parallel_dir = path;
    444     potential_parallel_dir.AppendFolder(parallel_dir);
    445     if (rtc::Filesystem::IsFolder(potential_parallel_dir)) {
    446       return potential_parallel_dir;
    447     }
    448 
    449     path.SetFolder(path.parent_folder());
    450   }
    451   return path;
    452 }
    453 
    454 inline rtc::Pathname GetGoogle3Directory() {
    455   return GetSiblingDirectory("google3");
    456 }
    457 
    458 inline rtc::Pathname GetTalkDirectory() {
    459   return GetSiblingDirectory("talk");
    460 }
    461 
    462 ///////////////////////////////////////////////////////////////////////////////
    463 // Unittest predicates which are similar to STREQ, but for raw memory
    464 ///////////////////////////////////////////////////////////////////////////////
    465 
    466 inline AssertionResult CmpHelperMemEq(const char* expected_expression,
    467                                       const char* expected_length_expression,
    468                                       const char* actual_expression,
    469                                       const char* actual_length_expression,
    470                                       const void* expected,
    471                                       size_t expected_length,
    472                                       const void* actual,
    473                                       size_t actual_length)
    474 {
    475   if ((expected_length == actual_length)
    476       && (0 == memcmp(expected, actual, expected_length))) {
    477     return AssertionSuccess();
    478   }
    479 
    480   Message msg;
    481   msg << "Value of: " << actual_expression
    482       << " [" << actual_length_expression << "]";
    483   if (true) {  //!actual_value.Equals(actual_expression)) {
    484     size_t buffer_size = actual_length * 2 + 1;
    485     char* buffer = STACK_ARRAY(char, buffer_size);
    486     hex_encode(buffer, buffer_size,
    487                reinterpret_cast<const char*>(actual), actual_length);
    488     msg << "\n  Actual: " << buffer << " [" << actual_length << "]";
    489   }
    490 
    491   msg << "\nExpected: " << expected_expression
    492       << " [" << expected_length_expression << "]";
    493   if (true) {  //!expected_value.Equals(expected_expression)) {
    494     size_t buffer_size = expected_length * 2 + 1;
    495     char* buffer = STACK_ARRAY(char, buffer_size);
    496     hex_encode(buffer, buffer_size,
    497                reinterpret_cast<const char*>(expected), expected_length);
    498     msg << "\nWhich is: " << buffer << " [" << expected_length << "]";
    499   }
    500 
    501   return AssertionFailure(msg);
    502 }
    503 
    504 inline AssertionResult CmpHelperFileEq(const char* expected_expression,
    505                                        const char* expected_length_expression,
    506                                        const char* actual_filename,
    507                                        const void* expected,
    508                                        size_t expected_length,
    509                                        const char* filename)
    510 {
    511   std::string contents;
    512   if (!ReadFile(filename, &contents)) {
    513     Message msg;
    514     msg << "File '" << filename << "' could not be read.";
    515     return AssertionFailure(msg);
    516   }
    517   return CmpHelperMemEq(expected_expression, expected_length_expression,
    518                         actual_filename, "",
    519                         expected, expected_length,
    520                         contents.c_str(), contents.size());
    521 }
    522 
    523 #define EXPECT_MEMEQ(expected, expected_length, actual, actual_length) \
    524   EXPECT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
    525                       actual, actual_length)
    526 
    527 #define ASSERT_MEMEQ(expected, expected_length, actual, actual_length) \
    528   ASSERT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
    529                       actual, actual_length)
    530 
    531 #define EXPECT_FILEEQ(expected, expected_length, filename) \
    532   EXPECT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
    533                       filename)
    534 
    535 #define ASSERT_FILEEQ(expected, expected_length, filename) \
    536   ASSERT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
    537                       filename)
    538 
    539 ///////////////////////////////////////////////////////////////////////////////
    540 // Helpers for initializing constant memory with integers in a particular byte
    541 // order
    542 ///////////////////////////////////////////////////////////////////////////////
    543 
    544 #define BYTE_CAST(x) static_cast<uint8>((x) & 0xFF)
    545 
    546 // Declare a N-bit integer as a little-endian sequence of bytes
    547 #define LE16(x) BYTE_CAST(((uint16)x) >>  0), BYTE_CAST(((uint16)x) >>  8)
    548 
    549 #define LE32(x) BYTE_CAST(((uint32)x) >>  0), BYTE_CAST(((uint32)x) >>  8), \
    550                 BYTE_CAST(((uint32)x) >> 16), BYTE_CAST(((uint32)x) >> 24)
    551 
    552 #define LE64(x) BYTE_CAST(((uint64)x) >>  0), BYTE_CAST(((uint64)x) >>  8), \
    553                 BYTE_CAST(((uint64)x) >> 16), BYTE_CAST(((uint64)x) >> 24), \
    554                 BYTE_CAST(((uint64)x) >> 32), BYTE_CAST(((uint64)x) >> 40), \
    555                 BYTE_CAST(((uint64)x) >> 48), BYTE_CAST(((uint64)x) >> 56)
    556 
    557 // Declare a N-bit integer as a big-endian (Internet) sequence of bytes
    558 #define BE16(x) BYTE_CAST(((uint16)x) >>  8), BYTE_CAST(((uint16)x) >>  0)
    559 
    560 #define BE32(x) BYTE_CAST(((uint32)x) >> 24), BYTE_CAST(((uint32)x) >> 16), \
    561                 BYTE_CAST(((uint32)x) >>  8), BYTE_CAST(((uint32)x) >>  0)
    562 
    563 #define BE64(x) BYTE_CAST(((uint64)x) >> 56), BYTE_CAST(((uint64)x) >> 48), \
    564                 BYTE_CAST(((uint64)x) >> 40), BYTE_CAST(((uint64)x) >> 32), \
    565                 BYTE_CAST(((uint64)x) >> 24), BYTE_CAST(((uint64)x) >> 16), \
    566                 BYTE_CAST(((uint64)x) >>  8), BYTE_CAST(((uint64)x) >>  0)
    567 
    568 // Declare a N-bit integer as a this-endian (local machine) sequence of bytes
    569 #ifndef BIG_ENDIAN
    570 #define BIG_ENDIAN 1
    571 #endif  // BIG_ENDIAN
    572 
    573 #if BIG_ENDIAN
    574 #define TE16 BE16
    575 #define TE32 BE32
    576 #define TE64 BE64
    577 #else  // !BIG_ENDIAN
    578 #define TE16 LE16
    579 #define TE32 LE32
    580 #define TE64 LE64
    581 #endif  // !BIG_ENDIAN
    582 
    583 ///////////////////////////////////////////////////////////////////////////////
    584 
    585 // Helpers for determining if X/screencasting is available (on linux).
    586 
    587 #define MAYBE_SKIP_SCREENCAST_TEST() \
    588   if (!testing::IsScreencastingAvailable()) { \
    589     LOG(LS_WARNING) << "Skipping test, since it doesn't have the requisite " \
    590                     << "X environment for screen capture."; \
    591     return; \
    592   } \
    593 
    594 #if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
    595 struct XDisplay {
    596   XDisplay() : display_(XOpenDisplay(NULL)) { }
    597   ~XDisplay() { if (display_) XCloseDisplay(display_); }
    598   bool IsValid() const { return display_ != NULL; }
    599   operator Display*() { return display_; }
    600  private:
    601   Display* display_;
    602 };
    603 #endif
    604 
    605 // Returns true if screencasting is available. When false, anything that uses
    606 // screencasting features may fail.
    607 inline bool IsScreencastingAvailable() {
    608 #if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
    609   XDisplay display;
    610   if (!display.IsValid()) {
    611     LOG(LS_WARNING) << "No X Display available.";
    612     return false;
    613   }
    614   int ignored_int, major_version, minor_version;
    615   if (!XRRQueryExtension(display, &ignored_int, &ignored_int) ||
    616       !XRRQueryVersion(display, &major_version, &minor_version) ||
    617       major_version < 1 ||
    618       (major_version < 2 && minor_version < 3)) {
    619     LOG(LS_WARNING) << "XRandr version: " << major_version << "."
    620                     << minor_version;
    621     LOG(LS_WARNING) << "XRandr is not supported or is too old (pre 1.3).";
    622     return false;
    623   }
    624 #endif
    625   return true;
    626 }
    627 }  // namespace testing
    628 
    629 #endif  // WEBRTC_BASE_TESTUTILS_H__
    630