Unit test for Encoder / Decoder for remoting

Unit test utils for Encoder and Decoder.

The tests will cover the state and messages sent out of the Encoder and
Decoder are in correct order.

It also make sure Decoded output matches input to the Encoder.

Review URL: http://codereview.chromium.org/3011010

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@53566 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/remoting/base/codec_test.cc b/remoting/base/codec_test.cc
new file mode 100644
index 0000000..b97293f
--- /dev/null
+++ b/remoting/base/codec_test.cc
@@ -0,0 +1,390 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <deque>
+#include <stdlib.h>
+
+#include "gfx/rect.h"
+#include "media/base/video_frame.h"
+#include "remoting/base/codec_test.h"
+#include "remoting/base/encoder.h"
+#include "remoting/base/mock_objects.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+static const int kWidth = 320;
+static const int kHeight = 240;
+static const int kBytesPerPixel = 4;
+
+// Some sample rects for testing.
+static const gfx::Rect kTestRects[] = {
+  gfx::Rect(0, 0, kWidth, kHeight),
+  gfx::Rect(0, 0, kWidth / 2, kHeight / 2),
+  gfx::Rect(kWidth / 2, kHeight / 2, kWidth / 2, kHeight / 2),
+  gfx::Rect(16, 16, 16, 16),
+  gfx::Rect(128, 64, 32, 32)
+};
+
+namespace remoting {
+
+// A class to test that the state transition of the Encoder is correct.
+class EncoderStateTester {
+ public:
+  EncoderStateTester()
+      : next_state_(Encoder::EncodingStarting) {
+  }
+
+  ~EncoderStateTester() {
+    EXPECT_EQ(Encoder::EncodingStarting, next_state_);
+  }
+
+  // Set the state output of the Encoder.
+  void ReceivedState(Encoder::EncodingState state) {
+    if (state & Encoder::EncodingStarting) {
+      EXPECT_EQ(Encoder::EncodingStarting, next_state_);
+      next_state_ = Encoder::EncodingInProgress | Encoder::EncodingEnded;
+    } else {
+      EXPECT_FALSE(next_state_ & Encoder::EncodingStarting);
+    }
+
+    if (state & Encoder::EncodingInProgress) {
+      EXPECT_TRUE(next_state_ & Encoder::EncodingInProgress);
+    }
+
+    if (state & Encoder::EncodingEnded) {
+      EXPECT_TRUE(next_state_ & Encoder::EncodingEnded);
+      next_state_ = Encoder::EncodingStarting;
+    }
+  }
+
+ private:
+  Encoder::EncodingState next_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(EncoderStateTester);
+};
+
+// A class to test the message output of the encoder.
+class EncoderMessageTester {
+ public:
+  EncoderMessageTester()
+      : begin_rect_(0),
+        rect_data_(0),
+        end_rect_(0),
+        state_(kWaitingForBeginRect),
+        strict_(false) {
+  }
+
+  ~EncoderMessageTester() {
+    EXPECT_EQ(begin_rect_, end_rect_);
+    EXPECT_EQ(kWaitingForBeginRect, state_);
+  }
+
+  // Test that we received the correct message.
+  void ReceivedMessage(HostMessage* message) {
+    EXPECT_TRUE(message->has_update_stream_packet());
+
+    if (state_ == kWaitingForBeginRect) {
+      EXPECT_TRUE(message->update_stream_packet().has_begin_rect());
+      state_ = kWaitingForRectData;
+      ++begin_rect_;
+
+      if (strict_) {
+        gfx::Rect rect = rects_.front();
+        rects_.pop_front();
+        EXPECT_EQ(rect.x(), message->update_stream_packet().begin_rect().x());
+        EXPECT_EQ(rect.y(), message->update_stream_packet().begin_rect().y());
+        EXPECT_EQ(rect.width(),
+                  message->update_stream_packet().begin_rect().width());
+        EXPECT_EQ(rect.height(),
+                  message->update_stream_packet().begin_rect().height());
+      }
+    } else {
+      EXPECT_FALSE(message->update_stream_packet().has_begin_rect());
+    }
+
+    if (state_ == kWaitingForRectData) {
+      if (message->update_stream_packet().has_rect_data()) {
+        ++rect_data_;
+      }
+
+      if (message->update_stream_packet().has_end_rect()) {
+        // Expect that we have received some data.
+        EXPECT_GT(rect_data_, 0);
+        rect_data_ = 0;
+        state_ = kWaitingForBeginRect;
+        ++end_rect_;
+      }
+    }
+  }
+
+  void set_strict(bool strict) {
+    strict_ = strict;
+  }
+
+  void AddRects(const gfx::Rect* rects, int count) {
+    rects_.insert(rects_.begin() + rects_.size(), rects, rects + count);
+  }
+
+ private:
+  enum State {
+    kWaitingForBeginRect,
+    kWaitingForRectData,
+  };
+
+  int begin_rect_;
+  int rect_data_;
+  int end_rect_;
+  State state_;
+  bool strict_;
+
+  std::deque<gfx::Rect> rects_;
+
+  DISALLOW_COPY_AND_ASSIGN(EncoderMessageTester);
+};
+
+class DecoderTester {
+ public:
+  DecoderTester(Decoder* decoder)
+      : strict_(false),
+        decoder_(decoder),
+        decode_done_(false) {
+    media::VideoFrame::CreateFrame(media::VideoFrame::RGB32,
+                                   kWidth, kHeight,
+                                   base::TimeDelta(),
+                                   base::TimeDelta(), &frame_);
+    EXPECT_TRUE(frame_.get());
+  }
+
+  void ReceivedMessage(HostMessage* message) {
+    if (message->has_update_stream_packet()) {
+      decoder_->PartialDecode(message);
+      return;
+    }
+
+    if (message->has_begin_update_stream()) {
+      decoder_->BeginDecode(
+          frame_, &update_rects_,
+          NewRunnableMethod(this, &DecoderTester::OnPartialDecodeDone),
+          NewRunnableMethod(this, &DecoderTester::OnDecodeDone));
+    }
+
+    if (message->has_end_update_stream()) {
+      decoder_->EndDecode();
+    }
+    delete message;
+    return;
+  }
+
+  void set_strict(bool strict) {
+    strict_ = strict;
+  }
+
+  void set_capture_data(scoped_refptr<CaptureData> data) {
+    capture_data_ = data;
+  }
+
+  void AddRects(const gfx::Rect* rects, int count) {
+    rects_.insert(rects_.begin() + rects_.size(), rects, rects + count);
+  }
+
+  bool decode_done() const { return decode_done_; }
+  void reset_decode_done() { decode_done_ = false; }
+
+ private:
+  void OnPartialDecodeDone() {
+    if (!strict_)
+      return;
+    for (size_t i = 0; i < update_rects_.size(); ++i) {
+      EXPECT_FALSE(rects_.empty());
+      gfx::Rect rect = rects_.front();
+      rects_.pop_front();
+      EXPECT_EQ(rect, update_rects_[i]);
+    }
+  }
+
+  void OnDecodeDone() {
+    decode_done_ = true;
+    if (!strict_)
+      return;
+
+    EXPECT_TRUE(capture_data_.get());
+    for (int i = 0; i < DataPlanes::kPlaneCount; ++i) {
+      if (!frame_->data(i) || !capture_data_->data_planes().data[i])
+        continue;
+      // TODO(hclam): HAndle YUV.
+      int size = capture_data_->data_planes().strides[i] * kHeight;
+      EXPECT_EQ(0, memcmp(capture_data_->data_planes().data[i],
+                          frame_->data(i), size));
+    }
+  }
+
+  bool strict_;
+  std::deque<gfx::Rect> rects_;
+  UpdatedRects update_rects_;
+  Decoder* decoder_;
+  scoped_refptr<media::VideoFrame> frame_;
+  scoped_refptr<CaptureData> capture_data_;
+  bool decode_done_;
+
+  DISALLOW_COPY_AND_ASSIGN(DecoderTester);
+};
+
+class EncoderTester {
+ public:
+  EncoderTester(EncoderMessageTester* message_tester,
+                EncoderStateTester* state_tester)
+      : message_tester_(message_tester),
+        state_tester_(state_tester),
+        decoder_tester_(NULL),
+        data_available_(0) {
+  }
+
+  ~EncoderTester() {
+    EXPECT_GT(data_available_, 0);
+  }
+
+  void DataAvailable(HostMessage* message,
+                     Encoder::EncodingState state) {
+    ++data_available_;
+    message_tester_->ReceivedMessage(message);
+    state_tester_->ReceivedState(state);
+
+    // Send the message to the DecoderTester.
+    if (decoder_tester_) {
+      if (state & Encoder::EncodingStarting) {
+        HostMessage* begin_update = new HostMessage();
+        begin_update->mutable_begin_update_stream();
+        decoder_tester_->ReceivedMessage(begin_update);
+      }
+
+      if (state & Encoder::EncodingInProgress) {
+        decoder_tester_->ReceivedMessage(message);
+      }
+
+      if (state & Encoder::EncodingEnded) {
+        HostMessage* end_update = new HostMessage();
+        end_update->mutable_end_update_stream();
+        decoder_tester_->ReceivedMessage(end_update);
+      }
+	} else {
+      delete message;
+	}
+  }
+
+  void AddRects(const gfx::Rect* rects, int count) {
+    message_tester_->AddRects(rects, count);
+  }
+
+  void set_decoder_tester(DecoderTester* decoder_tester) {
+    decoder_tester_ = decoder_tester;
+  }
+
+ private:
+  EncoderMessageTester* message_tester_;
+  EncoderStateTester* state_tester_;
+  DecoderTester* decoder_tester_;
+  int data_available_;
+
+  DISALLOW_COPY_AND_ASSIGN(EncoderTester);
+};
+
+scoped_refptr<CaptureData> PrepareEncodeData(PixelFormat format,
+                                             uint8** memory) {
+  // TODO(hclam): Support also YUV format.
+  CHECK(format == PixelFormatRgb32);
+  int size = kWidth * kHeight * kBytesPerPixel;
+
+  *memory = new uint8[size];
+  srand(0);
+  for (int i = 0; i < size; ++i) {
+    (*memory)[i] = rand() % 256;
+  }
+
+  DataPlanes planes;
+  memset(planes.data, 0, sizeof(planes.data));
+  memset(planes.strides, 0, sizeof(planes.strides));
+  planes.data[0] = *memory;
+  planes.strides[0] = kWidth * kBytesPerPixel;
+
+  scoped_refptr<CaptureData> data =
+      new CaptureData(planes, kWidth, kHeight, format);
+  return data;
+}
+
+static void TestEncodingRects(Encoder* encoder,
+                              EncoderTester* tester,
+                              scoped_refptr<CaptureData> data,
+                              const gfx::Rect* rects, int count) {
+  data->mutable_dirty_rects().clear();
+  data->mutable_dirty_rects().insert(
+      data->mutable_dirty_rects().begin(), rects, rects + count);
+  tester->AddRects(rects, count);
+
+  encoder->Encode(data, true,
+                  NewCallback(tester, &EncoderTester::DataAvailable));
+}
+
+void TestEncoder(Encoder* encoder, bool strict) {
+  EncoderMessageTester message_tester;
+  message_tester.set_strict(strict);
+
+  EncoderStateTester state_tester;
+  EncoderTester tester(&message_tester, &state_tester);
+
+  uint8* memory;
+  scoped_refptr<CaptureData> data =
+      PrepareEncodeData(PixelFormatRgb32, &memory);
+
+  TestEncodingRects(encoder, &tester, data, kTestRects, 1);
+  TestEncodingRects(encoder, &tester, data, kTestRects + 1, 1);
+  TestEncodingRects(encoder, &tester, data, kTestRects + 2, 1);
+  TestEncodingRects(encoder, &tester, data, kTestRects + 3, 2);
+  delete memory;
+}
+
+static void TestEncodingRects(Encoder* encoder,
+                              EncoderTester* encoder_tester,
+                              DecoderTester* decoder_tester,
+                              scoped_refptr<CaptureData> data,
+                              const gfx::Rect* rects, int count) {
+  data->mutable_dirty_rects().clear();
+  data->mutable_dirty_rects().insert(
+      data->mutable_dirty_rects().begin(), rects, rects + count);
+  encoder_tester->AddRects(rects, count);
+  decoder_tester->AddRects(rects, count);
+  decoder_tester->reset_decode_done();
+
+  encoder->Encode(data, true,
+                  NewCallback(encoder_tester, &EncoderTester::DataAvailable));
+  EXPECT_TRUE(decoder_tester->decode_done());
+}
+
+void TestEncoderDecoder(Encoder* encoder, Decoder* decoder, bool strict) {
+  EncoderMessageTester message_tester;
+  message_tester.set_strict(strict);
+
+  EncoderStateTester state_tester;
+  EncoderTester encoder_tester(&message_tester, &state_tester);
+
+  uint8* memory;
+  scoped_refptr<CaptureData> data =
+      PrepareEncodeData(PixelFormatRgb32, &memory);
+  DecoderTester decoder_tester(decoder);
+  decoder_tester.set_strict(strict);
+  decoder_tester.set_capture_data(data);
+  encoder_tester.set_decoder_tester(&decoder_tester);
+
+  TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
+                    kTestRects, 1);
+  TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
+                    kTestRects + 1, 1);
+  TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
+                    kTestRects + 2, 1);
+  TestEncodingRects(encoder, &encoder_tester, &decoder_tester, data,
+                    kTestRects + 3, 2);
+  delete memory;
+}
+
+}  // namespace remoting
+
+DISABLE_RUNNABLE_METHOD_REFCOUNT(remoting::DecoderTester);