Almost there...
git-svn-id: svn://chrome-svn/chromeos/trunk@24 06c00378-0e64-4dae-be16-12b19f9950a1
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
new file mode 100644
index 0000000..3dfc005
--- /dev/null
+++ b/download_action_unittest.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2009 The Chromium OS 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 <string>
+#include <vector>
+#include <glib.h>
+#include <gtest/gtest.h>
+#include "update_engine/action_pipe.h"
+#include "update_engine/download_action.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+class DownloadActionTest : public ::testing::Test { };
+
+namespace {
+class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ DownloadActionTestProcessorDelegate()
+ : loop_(NULL), processing_done_called_(false) {}
+ virtual ~DownloadActionTestProcessorDelegate() {
+ EXPECT_TRUE(processing_done_called_);
+ }
+ virtual void ProcessingDone(const ActionProcessor* processor) {
+ ASSERT_TRUE(loop_);
+ g_main_loop_quit(loop_);
+ vector<char> found_data = ReadFile(path_);
+ ASSERT_EQ(expected_data_.size(), found_data.size());
+ for (unsigned i = 0; i < expected_data_.size(); i++) {
+ EXPECT_EQ(expected_data_[i], found_data[i]);
+ }
+ processing_done_called_ = true;
+ }
+
+ virtual void ActionCompleted(const ActionProcessor* processor,
+ const AbstractAction* action,
+ bool success) {
+ // make sure actions always succeed
+ EXPECT_TRUE(success);
+ }
+
+ GMainLoop *loop_;
+ string path_;
+ vector<char> expected_data_;
+ bool processing_done_called_;
+};
+
+struct EntryPointArgs {
+ const vector<char> *data;
+ GMainLoop *loop;
+ ActionProcessor *processor;
+};
+
+gboolean StartProcessorInRunLoop(gpointer data) {
+ ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+ processor->StartProcessing();
+ return FALSE;
+}
+
+void TestWithData(const vector<char>& data, bool compress) {
+ vector<char> use_data;
+ if (compress) {
+ use_data = GzipCompressData(data);
+ } else {
+ use_data = data;
+ }
+
+ GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+ // TODO(adlr): see if we need a different file for build bots
+ const string path("/tmp/DownloadActionTest");
+ // takes ownership of passed in HttpFetcher
+ DownloadAction download_action("", path, 0,
+ OmahaHashCalculator::OmahaHashOfData(use_data),
+ compress, new MockHttpFetcher(&use_data[0],
+ use_data.size()));
+ DownloadActionTestProcessorDelegate delegate;
+ delegate.loop_ = loop;
+ delegate.expected_data_ = data;
+ delegate.path_ = path;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&download_action);
+
+ g_timeout_add(0, &StartProcessorInRunLoop, &processor);
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+
+ // remove temp file; don't care if there are errors here
+ unlink(path.c_str());
+}
+} // namespace {}
+
+TEST(DownloadActionTest, SimpleTest) {
+ vector<char> small;
+ const char* foo = "foo";
+ small.insert(small.end(), foo, foo + strlen(foo));
+ TestWithData(small, false);
+ TestWithData(small, true);
+}
+
+TEST(DownloadActionTest, LargeTest) {
+ vector<char> big(5 * kMockHttpFetcherChunkSize);
+ char c = '0';
+ for (unsigned int i = 0; i < big.size(); i++) {
+ big[i] = c;
+ if ('9' == c)
+ c = '0';
+ else
+ c++;
+ }
+ TestWithData(big, false);
+ TestWithData(big, true);
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingStopped(const ActionProcessor* processor) {
+ ASSERT_TRUE(loop_);
+ g_main_loop_quit(loop_);
+ }
+ GMainLoop *loop_;
+};
+
+gboolean TerminateEarlyTestStarter(gpointer data) {
+ ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+ processor->StartProcessing();
+ CHECK(processor->IsRunning());
+ processor->StopProcessing();
+ return FALSE;
+}
+
+} // namespace {}
+
+TEST(DownloadActionTest, TerminateEarlyTest) {
+ GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+ vector<char> data(kMockHttpFetcherChunkSize + kMockHttpFetcherChunkSize / 2);
+ memset(&data[0], 0, data.size());
+
+ const string path("/tmp/DownloadActionTest");
+ {
+ // takes ownership of passed in HttpFetcher
+ DownloadAction download_action("", path, 0, "", false,
+ new MockHttpFetcher(&data[0], data.size()));
+ TerminateEarlyTestProcessorDelegate delegate;
+ delegate.loop_ = loop;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&download_action);
+
+ g_timeout_add(0, &TerminateEarlyTestStarter, &processor);
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+ }
+
+ // 1 or 0 chunks should have come through
+ const off_t resulting_file_size(FileSize(path));
+ if (resulting_file_size != 0)
+ EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
+}
+
+class DownloadActionTestAction;
+
+template<>
+class ActionTraits<DownloadActionTestAction> {
+ public:
+ typedef string OutputObjectType;
+ typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+struct DownloadActionTestAction : public Action<DownloadActionTestAction> {
+ DownloadActionTestAction() : did_run_(false) {}
+ typedef string InputObjectType;
+ typedef string OutputObjectType;
+ ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {
+ did_run_ = true;
+ ASSERT_TRUE(HasInputObject());
+ EXPECT_EQ(expected_input_object_, GetInputObject());
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, true);
+ }
+ string Type() const { return "DownloadActionTestAction"; }
+ string expected_input_object_;
+ bool did_run_;
+};
+
+namespace {
+// This class is an ActionProcessorDelegate that simply terminates the
+// run loop when the ActionProcessor has completed processing. It's used
+// only by the test PassObjectOutTest.
+class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingDone(const ActionProcessor* processor) {
+ ASSERT_TRUE(loop_);
+ g_main_loop_quit(loop_);
+ }
+ GMainLoop *loop_;
+};
+
+gboolean PassObjectOutTestStarter(gpointer data) {
+ ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+ processor->StartProcessing();
+ return FALSE;
+}
+}
+
+TEST(DownloadActionTest, PassObjectOutTest) {
+ GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+ const string path("/tmp/DownloadActionTest");
+
+ // takes ownership of passed in HttpFetcher
+ DownloadAction download_action("", path, 0,
+ OmahaHashCalculator::OmahaHashOfString("x"),
+ false, new MockHttpFetcher("x", 1));
+
+ DownloadActionTestAction test_action;
+ test_action.expected_input_object_ = path;
+ BondActions(&download_action, &test_action);
+
+ ActionProcessor processor;
+ PassObjectOutTestProcessorDelegate delegate;
+ delegate.loop_ = loop;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&download_action);
+ processor.EnqueueAction(&test_action);
+
+ g_timeout_add(0, &PassObjectOutTestStarter, &processor);
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+
+ EXPECT_EQ(true, test_action.did_run_);
+}
+
+TEST(DownloadActionTest, BadOutFileTest) {
+ GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+ const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
+
+ // takes ownership of passed in HttpFetcher
+ DownloadAction download_action("", path, 0, "", false,
+ new MockHttpFetcher("x", 1));
+
+ ActionProcessor processor;
+ processor.EnqueueAction(&download_action);
+ processor.StartProcessing();
+ ASSERT_FALSE(processor.IsRunning());
+
+ g_main_loop_unref(loop);
+}
+
+} // namespace chromeos_update_engine