| // Copyright 2015 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <memory> |
| |
| #include "cobalt/loader/loader.h" |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/optional.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "cobalt/dom/url_utils.h" |
| #include "cobalt/loader/file_fetcher.h" |
| #include "cobalt/loader/text_decoder.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| using ::testing::_; |
| |
| namespace cobalt { |
| namespace loader { |
| namespace { |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // Callbacks |
| ////////////////////////////////////////////////////////////////////////// |
| |
| class TextDecoderCallback { |
| public: |
| explicit TextDecoderCallback(base::RunLoop* run_loop) : run_loop_(run_loop) {} |
| |
| void OnDone(const Origin&, std::unique_ptr<std::string> text) { |
| text_ = *text; |
| base::MessageLoop::current()->task_runner()->PostTask( |
| FROM_HERE, run_loop_->QuitClosure()); |
| } |
| |
| std::string text() { return text_; } |
| |
| private: |
| base::RunLoop* run_loop_; |
| std::string text_; |
| }; |
| |
| class LoaderCallback { |
| public: |
| LoaderCallback() : run_loop_(NULL) {} |
| explicit LoaderCallback(base::RunLoop* run_loop) : run_loop_(run_loop) {} |
| |
| void OnLoadComplete(const base::Optional<std::string>& text) { |
| if (!text) return; |
| |
| DLOG(ERROR) << *text; |
| if (run_loop_) |
| base::MessageLoop::current()->task_runner()->PostTask( |
| FROM_HERE, run_loop_->QuitClosure()); |
| } |
| |
| private: |
| base::RunLoop* run_loop_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // Mocks & Stubs |
| ////////////////////////////////////////////////////////////////////////// |
| |
| class MockDecoder : public Decoder { |
| public: |
| static std::unique_ptr<Decoder> Create( |
| MockDecoder* mock_decoder, |
| const loader::Loader::OnCompleteFunction& load_complete_callback = |
| loader::Loader::OnCompleteFunction()) { |
| return std::unique_ptr<Decoder>(mock_decoder); |
| } |
| |
| MOCK_METHOD2(DecodeChunk, void(const char*, size_t)); |
| MOCK_METHOD0(Finish, void()); |
| MOCK_METHOD0(Suspend, bool()); |
| MOCK_METHOD1(Resume, void(render_tree::ResourceProvider*)); |
| }; |
| |
| class MockFetcher : public Fetcher { |
| public: |
| explicit MockFetcher(Handler* handler) : Fetcher(handler) {} |
| |
| void FireError(const char* message) { handler()->OnError(this, message); } |
| |
| void FireReceived(const char* data, size_t size) { |
| handler()->OnReceived(this, data, size); |
| } |
| |
| void FireDone() { handler()->OnDone(this); } |
| |
| static std::unique_ptr<Fetcher> Create(Handler* handler) { |
| return std::unique_ptr<Fetcher>(new MockFetcher(handler)); |
| } |
| }; |
| |
| struct MockFetcherFactory { |
| public: |
| MockFetcherFactory() : count(0) {} |
| // Way to access the last fetcher created by the fetcher factory. |
| MockFetcher* fetcher; |
| int count; |
| |
| std::unique_ptr<Fetcher> Create(Fetcher::Handler* handler) { |
| fetcher = new MockFetcher(handler); |
| ++count; |
| return std::unique_ptr<Fetcher>(fetcher); |
| } |
| |
| Loader::FetcherCreator GetFetcherCreator() { |
| return base::Bind(&MockFetcherFactory::Create, base::Unretained(this)); |
| } |
| }; |
| |
| class MockLoaderCallback : public LoaderCallback { |
| public: |
| MockLoaderCallback() { |
| ON_CALL(*this, OnLoadComplete(_)) |
| .WillByDefault(Invoke(&real_, &LoaderCallback::OnLoadComplete)); |
| } |
| |
| MOCK_METHOD1(OnLoadComplete, void(const base::Optional<std::string>&)); |
| |
| private: |
| LoaderCallback real_; |
| }; |
| |
| } // namespace |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // LoaderTest |
| ////////////////////////////////////////////////////////////////////////// |
| |
| class LoaderTest : public ::testing::Test { |
| protected: |
| LoaderTest(); |
| ~LoaderTest() override {} |
| |
| base::FilePath data_dir_; |
| base::MessageLoop message_loop_; |
| }; |
| |
| LoaderTest::LoaderTest() : message_loop_(base::MessageLoop::TYPE_DEFAULT) { |
| data_dir_ = data_dir_.Append(FILE_PATH_LITERAL("cobalt")) |
| .Append(FILE_PATH_LITERAL("loader")) |
| .Append(FILE_PATH_LITERAL("testdata")); |
| } |
| |
| TEST_F(LoaderTest, FetcherError) { |
| MockLoaderCallback mock_loader_callback; |
| MockFetcherFactory mock_fetcher_factory; |
| MockDecoder* mock_decoder = new MockDecoder(); // To be owned by loader. |
| EXPECT_CALL(*mock_decoder, DecodeChunk(_, _)).Times(0); |
| EXPECT_CALL(*mock_decoder, Finish()).Times(0); |
| EXPECT_CALL(mock_loader_callback, OnLoadComplete(_)); |
| |
| Loader loader(mock_fetcher_factory.GetFetcherCreator(), |
| base::Bind(&MockDecoder::Create, mock_decoder), |
| base::Bind(&MockLoaderCallback::OnLoadComplete, |
| base::Unretained(&mock_loader_callback))); |
| mock_fetcher_factory.fetcher->FireError("Fail"); |
| } |
| |
| TEST_F(LoaderTest, FetcherSuspendAbort) { |
| MockLoaderCallback mock_loader_callback; |
| MockFetcherFactory mock_fetcher_factory; |
| MockDecoder* mock_decoder = new MockDecoder(); // To be owned by loader. |
| EXPECT_CALL(*mock_decoder, DecodeChunk(_, _)).Times(0); |
| EXPECT_CALL(*mock_decoder, Finish()).Times(0); |
| EXPECT_CALL(*mock_decoder, Suspend()); |
| EXPECT_CALL(*mock_decoder, Resume(_)).Times(0); |
| EXPECT_CALL(mock_loader_callback, OnLoadComplete(_)); |
| |
| Loader loader(mock_fetcher_factory.GetFetcherCreator(), |
| base::Bind(&MockDecoder::Create, mock_decoder), |
| base::Bind(&MockLoaderCallback::OnLoadComplete, |
| base::Unretained(&mock_loader_callback))); |
| loader.Suspend(); |
| } |
| |
| TEST_F(LoaderTest, FetcherSuspendResumeDone) { |
| MockLoaderCallback mock_loader_callback; |
| MockFetcherFactory mock_fetcher_factory; |
| MockDecoder* mock_decoder = new MockDecoder(); // To be owned by loader. |
| ON_CALL(*mock_decoder, Suspend()).WillByDefault(testing::Return(true)); |
| |
| EXPECT_CALL(*mock_decoder, Suspend()); |
| EXPECT_CALL(*mock_decoder, Resume(_)); |
| |
| EXPECT_CALL(*mock_decoder, DecodeChunk(_, _)).Times(0); |
| EXPECT_CALL(mock_loader_callback, OnLoadComplete(_)).Times(0); |
| EXPECT_CALL(*mock_decoder, Finish()); |
| |
| Loader loader(mock_fetcher_factory.GetFetcherCreator(), |
| base::Bind(&MockDecoder::Create, mock_decoder), |
| base::Bind(&MockLoaderCallback::OnLoadComplete, |
| base::Unretained(&mock_loader_callback))); |
| loader.Suspend(); |
| loader.Resume(NULL); |
| |
| // The fetcher should have been torn down and recreated. |
| EXPECT_EQ(2, mock_fetcher_factory.count); |
| mock_fetcher_factory.fetcher->FireDone(); |
| } |
| |
| TEST_F(LoaderTest, FetcherReceiveDone) { |
| InSequence dummy; |
| |
| MockLoaderCallback mock_loader_callback; |
| MockFetcherFactory mock_fetcher_factory; |
| MockDecoder* mock_decoder = new MockDecoder(); // To be owned by loader. |
| EXPECT_CALL(mock_loader_callback, OnLoadComplete(_)).Times(0); |
| EXPECT_CALL(*mock_decoder, DecodeChunk(_, _)); |
| EXPECT_CALL(*mock_decoder, Finish()); |
| |
| Loader loader(mock_fetcher_factory.GetFetcherCreator(), |
| base::Bind(&MockDecoder::Create, mock_decoder), |
| base::Bind(&MockLoaderCallback::OnLoadComplete, |
| base::Unretained(&mock_loader_callback))); |
| mock_fetcher_factory.fetcher->FireReceived(NULL, 0); |
| mock_fetcher_factory.fetcher->FireDone(); |
| } |
| |
| // Typical usage of Loader. |
| TEST_F(LoaderTest, ValidFileEndToEndTest) { |
| // Create a RunLoop that helps us use the base::MessageLoop, which is in the |
| // test fixture object. |
| base::RunLoop run_loop; |
| |
| // Create a loader, using a FileFetcher that loads from disk, and a |
| // TextDecoder that sees the received bytes as plain text. |
| const base::FilePath file_path = data_dir_.Append("performance-spike.html"); |
| FileFetcher::Options fetcher_options; |
| TextDecoderCallback text_decoder_callback(&run_loop); |
| LoaderCallback loader_callback(&run_loop); |
| Loader loader( |
| base::Bind(&FileFetcher::Create, file_path, fetcher_options), |
| base::Bind(&loader::TextDecoder::Create, |
| base::Bind(&TextDecoderCallback::OnDone, |
| base::Unretained(&text_decoder_callback))), |
| base::Bind(&LoaderCallback::OnLoadComplete, |
| base::Unretained(&loader_callback))); |
| |
| // When the message loop runs, the loader will start loading. It'll quit when |
| // loading is finished. |
| run_loop.Run(); |
| |
| // Get the loaded result from the decoder callback. |
| std::string loaded_text = text_decoder_callback.text(); |
| |
| // Compare the result with that of other API's. |
| std::string expected_text; |
| base::FilePath dir_test_data; |
| EXPECT_TRUE(base::PathService::Get(base::DIR_TEST_DATA, &dir_test_data)); |
| EXPECT_TRUE( |
| base::ReadFileToString(dir_test_data.Append(file_path), &expected_text)); |
| EXPECT_EQ(expected_text, loaded_text); |
| } |
| |
| } // namespace loader |
| } // namespace cobalt |