// 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 "base/scoped_ptr.h" #include "remoting/base/decoder.h" #include "remoting/client/chromoting_view.h" #include "remoting/proto/internal.pb.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::_; using ::testing::InSequence; using ::testing::Return; namespace remoting { class MockDecoder : public Decoder { public: MockDecoder() {} MOCK_METHOD4(BeginDecode, bool(scoped_refptr frame, UpdatedRects* updated_rects, Task* partial_decode_done, Task* decode_done)); MOCK_METHOD1(PartialDecode, bool(ChromotingHostMessage* message)); MOCK_METHOD0(EndDecode, void()); MOCK_METHOD0(Encoding, VideoPacketFormat::Encoding()); MOCK_METHOD0(IsStarted, bool()); private: DISALLOW_COPY_AND_ASSIGN(MockDecoder); }; // Fake ChromotingView that provides stub implementations for all pure virtual // methods. This is sufficient since we're only interested in testing the // base class methods in this file. class FakeView : public ChromotingView { bool Initialize() { return false; } void TearDown() {} void Paint() {} void SetSolidFill(uint32 color) {} void UnsetSolidFill() {} void SetConnectionState(ConnectionState s) {} void SetViewport(int x, int y, int width, int height) { frame_width_ = width; frame_height_ = height; } void SetHostScreenSize(int width, int height) {} void HandleBeginUpdateStream(ChromotingHostMessage* msg) {} void HandleUpdateStreamPacket(ChromotingHostMessage* msg) {} void HandleEndUpdateStream(ChromotingHostMessage* msg) {} public: // Testing accessors. // These provide access to private/protected members of ChromotingView so // that they can be tested/verified. Decoder* get_decoder() { return decoder_.get(); } void set_decoder(Decoder* decoder) { decoder_.reset(decoder); } // Testing wrappers for private setup/startup decoder routines. bool setup_decoder(VideoPacketFormat::Encoding encoding) { return SetupDecoder(encoding); } bool begin_decoding(Task* partial_decode_done, Task* decode_done) { return BeginDecoding(partial_decode_done, decode_done); } bool decode(ChromotingHostMessage* msg) { return Decode(msg); } bool end_decoding() { return EndDecoding(); } // Testing setup. void set_test_viewport() { SetViewport(0, 0, 640, 480); } }; // Verify the initial state. TEST(ChromotingViewTest, InitialState) { scoped_ptr view(new FakeView()); EXPECT_TRUE(view->get_decoder() == NULL); } // Test a simple decoder sequence: // HandleBeginUpdateStream: // HandleUpdateStreamPacket: // SetupDecoder - return false // BeginDecoding // Decode // HandleEndUpdateStream: // EndDecoding TEST(ChromotingViewTest, DecodeSimple) { scoped_ptr view(new FakeView()); view->set_test_viewport(); // HandleBeginUpdateStream // HandleUpdateStreamPacket ASSERT_TRUE(view->setup_decoder(EncodingZlib)); Decoder* decoder = view->get_decoder(); ASSERT_TRUE(decoder != NULL); EXPECT_EQ(EncodingZlib, decoder->Encoding()); EXPECT_FALSE(decoder->IsStarted()); // Overwrite |decoder_| with MockDecoder. MockDecoder* mock_decoder = new MockDecoder(); view->set_decoder(mock_decoder); EXPECT_CALL(*mock_decoder, Encoding()) .WillRepeatedly(Return(EncodingZlib)); { InSequence s; // BeginDecoding EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)); EXPECT_CALL(*mock_decoder, BeginDecode(_, _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(true)); // Decode EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder, PartialDecode(_)) .WillOnce(Return(true)); // EndDecoding EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder, EndDecode()) .Times(1); EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)); } // decoder_->IsStarted() is false, so we call begin_decoding(). ASSERT_TRUE(view->begin_decoding(NULL, NULL)); ASSERT_TRUE(view->decode(NULL)); // HandleEndUpdateStream ASSERT_TRUE(view->end_decoding()); } // Test a three-packet decoder sequence: // HandleBeginUpdateStream: // HandleUpdateStreamPacket: (1) // SetupDecoder - return false // BeginDecoding // Decode // HandleUpdateStreamPacket: (2) // SetupDecoder - return true // Decode // HandleUpdateStreamPacket: (3) // SetupDecoder - return true // Decode // HandleEndUpdateStream: // EndDecoding TEST(ChromotingViewTest, DecodeThreePackets) { scoped_ptr view(new FakeView()); view->set_test_viewport(); // HandleBeginUpdateStream // HandleUpdateStreamPacket (1) ASSERT_TRUE(view->setup_decoder(EncodingZlib)); Decoder* decoder = view->get_decoder(); ASSERT_TRUE(decoder != NULL); EXPECT_EQ(EncodingZlib, decoder->Encoding()); EXPECT_FALSE(decoder->IsStarted()); // Overwrite |decoder_| with MockDecoder. MockDecoder* mock_decoder = new MockDecoder(); view->set_decoder(mock_decoder); EXPECT_CALL(*mock_decoder, Encoding()) .WillRepeatedly(Return(EncodingZlib)); EXPECT_CALL(*mock_decoder, BeginDecode(_, _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder, PartialDecode(_)) .Times(3) .WillRepeatedly(Return(true)); EXPECT_CALL(*mock_decoder, EndDecode()) .Times(1); { InSequence s; // BeginDecoding EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)) .RetiresOnSaturation(); // BeginDecoding (1) // Decode (1) // SetupDecoder (1) // Decode (1) // SetupDecoder (1) // Decode (1) // EndDecoding (1) // Total = 7 calls EXPECT_CALL(*mock_decoder, IsStarted()) .Times(7) .WillRepeatedly(Return(true)) .RetiresOnSaturation(); // EndDecoding EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)) .RetiresOnSaturation(); } // decoder_->IsStarted() is false, so we call begin_decoding(). ASSERT_TRUE(view->begin_decoding(NULL, NULL)); ASSERT_TRUE(view->decode(NULL)); // HandleUpdateStreamPacket (2) ASSERT_TRUE(view->setup_decoder(EncodingZlib)); // Don't call BeginDecoding() because it is already started. ASSERT_TRUE(view->decode(NULL)); // HandleUpdateStreamPacket (3) ASSERT_TRUE(view->setup_decoder(EncodingZlib)); // Don't call BeginDecoding() because it is already started. ASSERT_TRUE(view->decode(NULL)); // HandleEndUpdateStream ASSERT_TRUE(view->end_decoding()); } // Test two update streams: (same packet encoding) // HandleBeginUpdateStream: // HandleUpdateStreamPacket: // SetupDecoder - return false // BeginDecoding // Decode // HandleEndUpdateStream: // EndDecoding // // HandleBeginUpdateStream: // HandleUpdateStreamPacket: // SetupDecoder - return false // BeginDecoding // Decode // HandleEndUpdateStream: // EndDecoding TEST(ChromotingViewTest, DecodeTwoStreams) { scoped_ptr view(new FakeView()); view->set_test_viewport(); // HandleBeginUpdateStream (update stream 1) // HandleUpdateStreamPacket ASSERT_TRUE(view->setup_decoder(EncodingZlib)); Decoder* decoder = view->get_decoder(); ASSERT_TRUE(decoder != NULL); EXPECT_EQ(EncodingZlib, decoder->Encoding()); EXPECT_FALSE(decoder->IsStarted()); // Overwrite |decoder_| with MockDecoder. MockDecoder* mock_decoder = new MockDecoder(); view->set_decoder(mock_decoder); EXPECT_CALL(*mock_decoder, Encoding()) .WillRepeatedly(Return(EncodingZlib)); EXPECT_CALL(*mock_decoder, BeginDecode(_, _, _, _)) .Times(2) .WillRepeatedly(Return(true)); EXPECT_CALL(*mock_decoder, PartialDecode(_)) .Times(2) .WillRepeatedly(Return(true)); EXPECT_CALL(*mock_decoder, EndDecode()) .Times(2); { InSequence s; // BeginDecoding EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)) .RetiresOnSaturation(); // BeginDecoding (1) // Decode (1) // EndDecoding (1) // Total = 3 calls EXPECT_CALL(*mock_decoder, IsStarted()) .Times(3) .WillRepeatedly(Return(true)) .RetiresOnSaturation(); // EndDecoding (1) // SetupDecoder (1) // BeginDecoding (1) // Total = 3 calls EXPECT_CALL(*mock_decoder, IsStarted()) .Times(3) .WillRepeatedly(Return(false)) .RetiresOnSaturation(); // BeginDecoding (1) // Decode (1) // EndDecoding (1) // Total = 3 calls EXPECT_CALL(*mock_decoder, IsStarted()) .Times(3) .WillRepeatedly(Return(true)) .RetiresOnSaturation(); // EndDecoding EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)) .RetiresOnSaturation(); } // |started| is false, so we call begin_decoding(). ASSERT_TRUE(view->begin_decoding(NULL, NULL)); ASSERT_TRUE(view->decode(NULL)); // HandleEndUpdateStream ASSERT_TRUE(view->end_decoding()); // HandleBeginUpdateStream (update stream 2) // HandleUpdateStreamPacket Decoder* old_decoder = view->get_decoder(); view->setup_decoder(EncodingZlib); // Verify we're using the same decoder since the encoding matches. Decoder* new_decoder = view->get_decoder(); ASSERT_TRUE(new_decoder == old_decoder); // |started| is false, so we call begin_decoding(). ASSERT_TRUE(view->begin_decoding(NULL, NULL)); ASSERT_TRUE(view->decode(NULL)); // HandleEndUpdateStream ASSERT_TRUE(view->end_decoding()); } // Test two update streams with different encodings: // HandleBeginUpdateStream: // HandleUpdateStreamPacket: ('Zlib' encoded) // SetupDecoder // BeginDecoding // Decode // HandleEndUpdateStream: // EndDecoding // // HandleBeginUpdateStream: // HandleUpdateStreamPacket: ('None' encoded) // SetupDecoder // BeginDecoding // Decode // HandleEndUpdateStream: // EndDecoding TEST(ChromotingViewTest, DecodeTwoStreamsDifferentEncodings) { scoped_ptr view(new FakeView()); view->set_test_viewport(); // HandleBeginUpdateStream (update stream 1) // HandleUpdateStreamPacket ASSERT_TRUE(view->setup_decoder(EncodingZlib)); Decoder* decoder = view->get_decoder(); ASSERT_TRUE(decoder != NULL); EXPECT_EQ(EncodingZlib, decoder->Encoding()); EXPECT_FALSE(decoder->IsStarted()); // Overwrite |decoder_| with MockDecoder. MockDecoder* mock_decoder1 = new MockDecoder(); view->set_decoder(mock_decoder1); EXPECT_CALL(*mock_decoder1, Encoding()) .WillRepeatedly(Return(EncodingZlib)); EXPECT_CALL(*mock_decoder1, BeginDecode(_, _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder1, PartialDecode(_)) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder1, EndDecode()); { InSequence s1; // BeginDecoding EXPECT_CALL(*mock_decoder1, IsStarted()) .WillOnce(Return(false)) .RetiresOnSaturation(); // BeginDecoding (1) // Decode (1) // EndDecoding (1) // Total = 3 calls EXPECT_CALL(*mock_decoder1, IsStarted()) .Times(3) .WillRepeatedly(Return(true)) .RetiresOnSaturation(); // EndDecoding (1) // SetupDecoder (1) // Total = 2 calls EXPECT_CALL(*mock_decoder1, IsStarted()) .Times(2) .WillRepeatedly(Return(false)) .RetiresOnSaturation(); } // |started| is false, so we call begin_decoding(). ASSERT_TRUE(view->begin_decoding(NULL, NULL)); ASSERT_TRUE(view->decode(NULL)); // HandleEndUpdateStream ASSERT_TRUE(view->end_decoding()); // HandleBeginUpdateStream (update stream 2) // HandleUpdateStreamPacket // Encoding for second stream is different from first, so this will // create a new decoder. ASSERT_TRUE(view->setup_decoder(EncodingNone)); // The decoder should be new. EXPECT_NE(mock_decoder1, view->get_decoder()); // Overwrite |decoder_| with MockDecoder. MockDecoder* mock_decoder2 = new MockDecoder(); view->set_decoder(mock_decoder2); EXPECT_CALL(*mock_decoder2, Encoding()) .WillRepeatedly(Return(EncodingNone)); EXPECT_CALL(*mock_decoder2, BeginDecode(_, _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder2, PartialDecode(_)) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder2, EndDecode()); { InSequence s2; // BeginDecoding EXPECT_CALL(*mock_decoder2, IsStarted()) .WillOnce(Return(false)) .RetiresOnSaturation(); // BeginDecoding (1) // Decode (1) // EndDecoding (1) // Total = 3 calls EXPECT_CALL(*mock_decoder2, IsStarted()) .Times(3) .WillRepeatedly(Return(true)) .RetiresOnSaturation(); // EndDecoding EXPECT_CALL(*mock_decoder2, IsStarted()) .WillOnce(Return(false)) .RetiresOnSaturation(); } // |started| is false, so we call begin_decoding(). ASSERT_TRUE(view->begin_decoding(NULL, NULL)); ASSERT_TRUE(view->decode(NULL)); // HandleEndUpdateStream ASSERT_TRUE(view->end_decoding()); } // Test failure when packets in a stream have mismatched encodings. // HandleBeginUpdateStream: // HandleUpdateStreamPacket: (1) // SetupDecoder - encoding = Zlib // BeginDecoding // Decode // HandleUpdateStreamPacket: (2) // SetupDecoder - encoding = none // DEATH TEST(ChromotingViewTest, MismatchedEncodings) { scoped_ptr view(new FakeView()); view->set_test_viewport(); // HandleBeginUpdateStream // HandleUpdateStreamPacket (1) // encoding = Zlib ASSERT_TRUE(view->setup_decoder(EncodingZlib)); Decoder* decoder = view->get_decoder(); ASSERT_TRUE(decoder != NULL); EXPECT_EQ(EncodingZlib, decoder->Encoding()); EXPECT_FALSE(decoder->IsStarted()); // Overwrite |decoder_| with MockDecoder. MockDecoder* mock_decoder = new MockDecoder(); view->set_decoder(mock_decoder); EXPECT_CALL(*mock_decoder, Encoding()) .WillRepeatedly(Return(EncodingZlib)); { InSequence s; // BeginDecoding(). EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)); EXPECT_CALL(*mock_decoder, BeginDecode(_, _, _, _)) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(true)); // Decode(). EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(true)); EXPECT_CALL(*mock_decoder, PartialDecode(_)) .WillOnce(Return(true)); // SetupDecoder(). EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(true)); } // |started| is false, so we call begin_decoding(). ASSERT_TRUE(view->begin_decoding(NULL, NULL)); ASSERT_TRUE(view->decode(NULL)); // HandleUpdateStreamPacket (2) // encoding = None // Doesn't match previous packet encoding, so returns failure. ASSERT_FALSE(view->setup_decoder(EncodingNone)); } // Verify we fail when Decode() is called without first calling // BeginDecoding(). TEST(ChromotingViewTest, MissingBegin) { scoped_ptr view(new FakeView()); view->set_test_viewport(); ASSERT_TRUE(view->setup_decoder(EncodingZlib)); ASSERT_TRUE(view->get_decoder() != NULL); // Overwrite |decoder_| with MockDecoder. MockDecoder* mock_decoder = new MockDecoder(); view->set_decoder(mock_decoder); EXPECT_CALL(*mock_decoder, IsStarted()) .WillOnce(Return(false)); // Call decode() without calling begin_decoding(). EXPECT_FALSE(view->decode(NULL)); } // Test requesting a decoder for an invalid encoding. TEST(ChromotingViewTest, InvalidEncoding) { scoped_ptr view(new FakeView()); EXPECT_FALSE(view->setup_decoder(EncodingInvalid)); } } // namespace remoting