summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
authorclintstaley@chromium.org <clintstaley@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-05 09:48:34 +0000
committerclintstaley@chromium.org <clintstaley@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-05 09:48:34 +0000
commitadb6aeae8b6ac7b15a114412833b65c973c51daf (patch)
treea6a9855475dc19fcae8b7f2d88cb772676fb585a /chrome/browser/extensions
parent5217a27d15e102d5d56bded9e6553117fdd0c9ae (diff)
downloadchromium_src-adb6aeae8b6ac7b15a114412833b65c973c51daf.zip
chromium_src-adb6aeae8b6ac7b15a114412833b65c973c51daf.tar.gz
chromium_src-adb6aeae8b6ac7b15a114412833b65c973c51daf.tar.bz2
Refactored and revised extensions record API with associated browser test
BUG=123968 TEST= Review URL: http://codereview.chromium.org/9716010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@135539 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r--chrome/browser/extensions/extension_function_registry.cc5
-rw-r--r--chrome/browser/extensions/extension_record_api.cc244
-rw-r--r--chrome/browser/extensions/extension_record_api.h158
-rw-r--r--chrome/browser/extensions/extension_record_api_test.cc297
4 files changed, 704 insertions, 0 deletions
diff --git a/chrome/browser/extensions/extension_function_registry.cc b/chrome/browser/extensions/extension_function_registry.cc
index 8261b43..a3c6d53 100644
--- a/chrome/browser/extensions/extension_function_registry.cc
+++ b/chrome/browser/extensions/extension_function_registry.cc
@@ -38,6 +38,7 @@
#include "chrome/browser/extensions/extension_page_capture_api.h"
#include "chrome/browser/extensions/extension_preference_api.h"
#include "chrome/browser/extensions/extension_processes_api.h"
+#include "chrome/browser/extensions/extension_record_api.h"
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/extensions/extension_test_api.h"
#include "chrome/browser/extensions/extension_web_socket_proxy_private_api.h"
@@ -210,6 +211,10 @@ void ExtensionFunctionRegistry::ResetFunctions() {
RegisterFunction<MetricsRecordMediumTimeFunction>();
RegisterFunction<MetricsRecordLongTimeFunction>();
+ // Record.
+ RegisterFunction<CaptureURLsFunction>();
+ RegisterFunction<ReplayURLsFunction>();
+
// RLZ.
#if defined(OS_WIN) || defined(OS_MACOSX)
RegisterFunction<RlzRecordProductEventFunction>();
diff --git a/chrome/browser/extensions/extension_record_api.cc b/chrome/browser/extensions/extension_record_api.cc
new file mode 100644
index 0000000..9040ee9
--- /dev/null
+++ b/chrome/browser/extensions/extension_record_api.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2012 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 "chrome/browser/extensions/extension_record_api.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/process_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/api/experimental.record.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_switches.h"
+
+namespace record = extensions::api::experimental_record;
+
+ProcessStrategy::~ProcessStrategy() {}
+
+void ProductionProcessStrategy::RunProcess(const CommandLine& line) {
+ base::LaunchOptions options;
+
+ options.wait = true;
+ base::LaunchProcess(line, options, NULL);
+}
+
+RunPageCyclerFunction::RunPageCyclerFunction(ProcessStrategy* strategy)
+ : base_command_line_(*CommandLine::ForCurrentProcess()),
+ process_strategy_(strategy) {}
+
+RunPageCyclerFunction::~RunPageCyclerFunction() {}
+
+bool RunPageCyclerFunction::RunImpl() {
+ if (!ParseJSParameters())
+ return false;
+
+ // If we've had any errors reportable to the JS caller so far (in
+ // parameter parsing) then return a list of such errors, else perform
+ // RunTestBrowser on the BlockingPool.
+ if (!errors_.empty()) {
+ result_.reset(record::CaptureURLs::Result::Create(errors_));
+ SendResponse(true);
+ } else {
+ content::BrowserThread::PostBlockingPoolTask(FROM_HERE,
+ base::Bind(&RunPageCyclerFunction::RunTestBrowser, this));
+ process_strategy_->PumpBlockingPool(); // Test purposes only.
+ }
+
+ return true;
+}
+
+CommandLine RunPageCyclerFunction::RemoveSwitches(const CommandLine& original,
+ const std::vector<std::string>& to_remove) {
+ std::vector<const char*> to_keep;
+ const CommandLine::SwitchMap& current_switches = original.GetSwitches();
+ CommandLine filtered(original.GetProgram());
+
+ // Retain in |to_keep| all current swtiches *not* in |to_remove|.
+ for (CommandLine::SwitchMap::const_iterator itr = current_switches.begin();
+ itr != current_switches.end(); ++itr) {
+ if (std::find(to_remove.begin(), to_remove.end(), (*itr).first) ==
+ to_remove.end()) {
+ to_keep.push_back((*itr).first.c_str());
+ }
+ }
+
+ // Rely on std::vector keeping its contents in contiguous order.
+ // (This is documented STL spec.)
+ filtered.CopySwitchesFrom(original, &to_keep.front(), to_keep.size());
+
+ return filtered;
+}
+
+// Runs on BlockingPool thread. Invoked from UI thread and passes back to
+// UI thread for |Final()| callback to JS side.
+void RunPageCyclerFunction::RunTestBrowser() {
+ // Remove any current switch settings that would interfere with test browser
+ // commandline setup.
+ std::vector<std::string> remove_switches;
+ remove_switches.push_back(switches::kUserDataDir);
+ remove_switches.push_back(switches::kVisitURLs);
+ remove_switches.push_back(switches::kVisitURLsCount);
+ remove_switches.push_back(switches::kPlaybackMode);
+ remove_switches.push_back(switches::kRecordStats);
+ remove_switches.push_back(switches::kLoadExtension);
+
+ CommandLine line = RemoveSwitches(base_command_line_, remove_switches);
+
+ // Add the user-data-dir switch, since this is common to both call types.
+ line.AppendSwitchPath(switches::kUserDataDir, user_data_dir_);
+
+ // Do the same for visit-urls, creating a temp file to communicate the
+ // URL list to the test browser.
+ FilePath url_path;
+ file_util::CreateTemporaryFile(&url_path);
+ file_util::WriteFile(url_path, url_contents_.c_str(), url_contents_.size());
+ line.AppendSwitchPath(switches::kVisitURLs, url_path);
+
+ // Set up Capture- or Replay-specific commandline switches.
+ AddSwitches(&line);
+ LOG(ERROR) << "Test browser commandline: " << line.GetCommandLineString();
+
+ // Run the test browser (or a mockup, depending on |process_strategy_|.
+ process_strategy_->RunProcess(line);
+
+ // Read URL errors file if there is one, and save errors in |errors_|.
+ // Odd extension handling needed because temp files have lots of "."s in
+ // their names, and we need to cleanly add kURLErrorsSuffix as a final
+ // extension.
+ FilePath error_file_path = url_path.DirName()
+ .Append(url_path.BaseName().value() +
+ FilePath::StringType(kURLErrorsSuffix));
+
+ if (file_util::PathExists(error_file_path)) {
+ std::string error_content;
+ file_util::ReadFileToString(error_file_path, &error_content);
+
+ base::SplitString(error_content, '\n', &errors_);
+ }
+
+ // Do any special post-test-browser file reading (e.g. stats report))
+ // while we're on the BlockingPool thread.
+ ReadReplyFiles();
+
+ // Back to UI thread to finish up the JS call.
+ content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&RunPageCyclerFunction::Finish, this));
+}
+
+const ProcessStrategy &RunPageCyclerFunction::GetProcessStrategy() {
+ return *process_strategy_;
+}
+
+// CaptureURLsFunction ------------------------------------------------
+
+CaptureURLsFunction::CaptureURLsFunction()
+ : RunPageCyclerFunction(new ProductionProcessStrategy()) {}
+
+CaptureURLsFunction::CaptureURLsFunction(ProcessStrategy* strategy)
+ : RunPageCyclerFunction(strategy) {}
+
+// Fetch data for possible optional switches for a repeat count and an
+// extension to load.
+bool CaptureURLsFunction::ParseJSParameters() {
+ scoped_ptr<record::CaptureURLs::Params> params(
+ record::CaptureURLs::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ url_contents_ = JoinString(params->urls, '\n');
+ user_data_dir_ = FilePath::FromUTF8Unsafe(params->cache_directory_path);
+
+ return true;
+}
+
+// CaptureURLsFunction adds "record-mode" to sub-browser call, and returns
+// just the (possibly empty) error list.
+void CaptureURLsFunction::AddSwitches(CommandLine* line) {
+ if (!line->HasSwitch(switches::kRecordMode))
+ line->AppendSwitch(switches::kRecordMode);
+}
+
+void CaptureURLsFunction::Finish() {
+ result_.reset(record::CaptureURLs::Result::Create(errors_));
+ SendResponse(true);
+}
+
+
+// ReplayURLsFunction ------------------------------------------------
+
+ReplayURLsFunction::ReplayURLsFunction()
+ : RunPageCyclerFunction(new ProductionProcessStrategy()),
+ repeat_count_(1), run_time_ms_(0) {
+}
+
+ReplayURLsFunction::ReplayURLsFunction(ProcessStrategy* strategy)
+ : RunPageCyclerFunction(strategy),
+ repeat_count_(1), run_time_ms_(0) {
+}
+
+ReplayURLsFunction::~ReplayURLsFunction() {}
+
+// Fetch data for possible optional switches for a repeat count and an
+// extension to load.
+bool ReplayURLsFunction::ParseJSParameters() {
+ scoped_ptr<record::ReplayURLs::Params> params(
+ record::ReplayURLs::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ url_contents_ = JoinString(params->urls, '\n');
+ user_data_dir_ = FilePath::FromUTF8Unsafe(params->capture_directory_path);
+
+ if (params->details.get()) {
+ if (params->details->repeat_count.get())
+ repeat_count_ = *params->details->repeat_count;
+ if (params->details->extension_path.get())
+ extension_path_ =
+ FilePath::FromUTF8Unsafe(*params->details->extension_path);
+ }
+
+ return true;
+}
+
+// Add special switches, if indicated, for repeat count and extension to load,
+// plus temp file into which to place stats. (Can't do this in
+// ParseJSParameters because file creation can't go on the UI thread.)
+// Plus, initialize time to create run time statistic.
+void ReplayURLsFunction::AddSwitches(CommandLine* line) {
+ file_util::CreateTemporaryFile(&stats_file_path_);
+
+ line->AppendSwitchASCII(switches::kVisitURLsCount,
+ base::Int64ToString(repeat_count_));
+ if (!extension_path_.empty())
+ line->AppendSwitchPath(switches::kLoadExtension, extension_path_);
+ line->AppendSwitch(switches::kPlaybackMode);
+ line->AppendSwitchPath(switches::kRecordStats, stats_file_path_);
+
+ timer_ = base::Time::NowFromSystemTime();
+}
+
+// Read stats file, and get run time.
+void ReplayURLsFunction::ReadReplyFiles() {
+ file_util::ReadFileToString(stats_file_path_, &stats_);
+
+ run_time_ms_ = (base::Time::NowFromSystemTime() - timer_).InMilliseconds();
+}
+
+void ReplayURLsFunction::Finish() {
+ record::ReplayURLsResult result;
+
+ result.run_time = run_time_ms_;
+ result.stats = stats_;
+ result.errors = errors_;
+
+ result_.reset(record::ReplayURLs::Result::Create(result));
+ SendResponse(true);
+}
+
diff --git a/chrome/browser/extensions/extension_record_api.h b/chrome/browser/extensions/extension_record_api.h
new file mode 100644
index 0000000..afd6cfd
--- /dev/null
+++ b/chrome/browser/extensions/extension_record_api.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_RECORD_API_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_RECORD_API_H_
+#pragma once
+
+#include "chrome/browser/extensions/extension_function.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/time.h"
+
+namespace {
+
+const FilePath::CharType kURLErrorsSuffix[] = FILE_PATH_LITERAL(".errors");
+const char kErrorsKey[] = "errors";
+const char kStatsKey[] = "stats";
+
+};
+
+// ProcessStrategy abstracts the API's starting and waiting on a test
+// browser instance. This lets us browser-test the API without actually
+// firing up a sub browser instance.
+class ProcessStrategy {
+ public:
+ // Needed to void build warnings
+ virtual ~ProcessStrategy();
+
+ // Used only in test version to pump the blocking pool queue,
+ // which doesn't otherwise happen during test.
+ virtual void PumpBlockingPool() {}
+
+ // Start up process with given commandline. Real version does just
+ // that; test version mocks it up, generating errors or good results,
+ // as configured.
+ virtual void RunProcess(const CommandLine& line) = 0;
+};
+
+// Production (default) version of ProcessStrategy. See ProcessStrategy
+// comments for more info. This subclass actually starts a sub browser
+// instance.
+class ProductionProcessStrategy : public ProcessStrategy {
+ public:
+ virtual void RunProcess(const CommandLine& line) OVERRIDE;
+};
+
+// Both page cycler calls (capture and replay) have a great deal in common,
+// including the need to build and write a url list file, set the user
+// data dir, start a sub-instance of Chrome, and parse a resultant error
+// file. This base class encapslates those common elements.
+class RunPageCyclerFunction : public AsyncExtensionFunction {
+ public:
+
+ explicit RunPageCyclerFunction(ProcessStrategy* strategy);
+ virtual ~RunPageCyclerFunction();
+
+ // Gather common page cycler parameters and store them, then do blocking
+ // thread invocation of RunTestBrowser.
+ virtual bool RunImpl() OVERRIDE;
+
+ // Make a CommandLine copy of |original|, removing all switches in
+ // |to_remove|.
+ static CommandLine RemoveSwitches(const CommandLine& original,
+ const std::vector<std::string>& to_remove);
+
+ // Return ProcessStrategy, to allow for test versions.
+ virtual const ProcessStrategy &GetProcessStrategy();
+
+ protected:
+ // Parse the JS parameters, and store them as member data.
+ virtual bool ParseJSParameters() = 0;
+
+ // Do a vanilla test browser run, bracketing it immediately before and
+ // after with a call of AddSwitches to add special commandline options
+ // for Capture or Replay, and ReadReplyFiles to do any special post-run
+ // data collection. Gather any error results into |errors_| and then do a
+ // BrowserThread call of Finish to return JS values.
+ virtual void RunTestBrowser();
+ virtual void AddSwitches(CommandLine* command_line) {}
+
+ // The test browser communicates URL errors, performance statistics, and
+ // possibly other data by posting them to text files. ReadReplyFiles
+ // collects these data for return to the JS side caller.
+ virtual void ReadReplyFiles() {}
+
+ // Return the values gathered in RunTestBrowser. No common code here; all
+ // logic is in subclasses.
+ virtual void Finish() {}
+
+ FilePath user_data_dir_;
+ std::string url_contents_;
+ std::vector<std::string> errors_;
+
+ // Base CommandLine on which to build the test browser CommandLine
+ CommandLine base_command_line_;
+
+ // ProcessStrategy to use for this run.
+ scoped_ptr<ProcessStrategy> process_strategy_;
+};
+
+class CaptureURLsFunction : public RunPageCyclerFunction {
+ public:
+ CaptureURLsFunction();
+ explicit CaptureURLsFunction(ProcessStrategy* strategy);
+
+ private:
+ virtual ~CaptureURLsFunction() {}
+
+ // Read the ReplayDetails parameter if it exists.
+ virtual bool ParseJSParameters() OVERRIDE;
+
+ // Add record-mode.
+ virtual void AddSwitches(CommandLine* command_line) OVERRIDE;
+
+ // Return error list.
+ virtual void Finish() OVERRIDE;
+
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.record.captureURLs");
+};
+
+class ReplayURLsFunction : public RunPageCyclerFunction {
+ public:
+ ReplayURLsFunction();
+ explicit ReplayURLsFunction(ProcessStrategy* strategy);
+
+ private:
+ virtual ~ReplayURLsFunction();
+
+ // Read the ReplayDetails parameter if it exists.
+ virtual bool ParseJSParameters() OVERRIDE;
+
+ // Add visit-urls-count and load-extension.
+ virtual void AddSwitches(CommandLine* command_line) OVERRIDE;
+
+ // Read stats file.
+ virtual void ReadReplyFiles() OVERRIDE;
+
+ // Return error list, statistical results, and runtime.
+ virtual void Finish() OVERRIDE;
+
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.record.replayURLs");
+
+ // These three data are additional information added to the sub-browser
+ // commandline.
+ int repeat_count_;
+ FilePath extension_path_;
+ FilePath stats_file_path_;
+
+ // This time datum marks the start and end of the sub-browser run.
+ base::Time timer_;
+
+ // These two data are additional information returned to caller.
+ int run_time_ms_;
+ std::string stats_;
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_RECORD_API_H_
diff --git a/chrome/browser/extensions/extension_record_api_test.cc b/chrome/browser/extensions/extension_record_api_test.cc
new file mode 100644
index 0000000..6ba23a8
--- /dev/null
+++ b/chrome/browser/extensions/extension_record_api_test.cc
@@ -0,0 +1,297 @@
+// Copyright (c) 2012 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 "chrome/browser/extensions/extension_record_api.h"
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/string_split.h"
+#include "base/stringprintf.h"
+#include "base/string_util.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/api/experimental.record.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_switches.h"
+
+namespace utils = extension_function_test_utils;
+
+namespace {
+
+// Dummy content for mock stats file.
+const std::string kTestStatistics = "Sample Stat 1\nSample Stat 2\n";
+
+// Standard capture parameters, with a mix of good and bad URLs, and
+// a hole for filling in the user data dir.
+const char kCaptureArgs1[] =
+ "[[\"URL 1\", \"URL 2(bad)\", \"URL 3\", \"URL 4(bad)\"], \"%s\"]";
+
+// Standard playback parameters, with the same mix of good and bad URLs
+// as the capture parameters, a hole for filling in the user data dir, and
+// a mocked-up extension path and repeat count (which are used only to
+// verify that they made it into the CommandLine, since extension loading
+// and repeat-counting are hard to emulate in the test ProcessStrategy.
+const char kPlaybackArgs1[] =
+ "[[\"URL 1\", \"URL 2(bad)\", \"URL 3\", \"URL 4(bad)\"], \"%s\""
+ ", {\"extensionPath\": \"MockExtension\", \"repeatCount\": 2}]";
+
+// Use this as the value of FilePath switches (e.g. user-data-dir) that
+// should be replaced by the record methods.
+const FilePath::CharType kDummyDirName[] = FILE_PATH_LITERAL("ReplaceMe");
+
+// Use this as the filename for a mock "cache" file in the user-data-dir.
+const FilePath::CharType kMockCacheFile[] = FILE_PATH_LITERAL("MockCache");
+
+// Prefix for temporary user data directory
+const FilePath::CharType kUserDataDirPrefix[]
+ = FILE_PATH_LITERAL("PageCyclerTest");
+
+}
+
+class TestProcessStrategy : public ProcessStrategy {
+ public:
+ TestProcessStrategy() : command_line_(CommandLine::NO_PROGRAM) {}
+ ~TestProcessStrategy() {}
+
+ // Pump the blocking pool queue, since this is needed during test.
+ void PumpBlockingPool() OVERRIDE {
+ content::BrowserThread::GetBlockingPool()->FlushForTesting();
+ }
+
+ // Act somewhat like a real test browser instance. In particular:
+ // 1. If visit-urls, then
+ // a. If record-mode, then put a copy of the URL list into the user data
+ // directory in a mocked up cache file
+ // b. If playback-mode, then check for existence of that URL list copy
+ // in the cache
+ // c. Scan list of URLS, noting in |urls_visited_| all those
+ // visited. If there are any "bad" URLS, don't visit these, but
+ // create a ".errors" file listing them.
+ // 2. If record-stats, then create a mock stats file.
+ void RunProcess(const CommandLine& command_line) OVERRIDE {
+ command_line_ = command_line;
+ visited_urls_.clear();
+
+ if (command_line.HasSwitch(switches::kVisitURLs)) {
+ FilePath url_path =
+ command_line.GetSwitchValuePath(switches::kVisitURLs);
+
+ if (command_line.HasSwitch(switches::kRecordMode) ||
+ command_line.HasSwitch(switches::kPlaybackMode)) {
+ FilePath url_path_copy = command_line.GetSwitchValuePath(
+ switches::kUserDataDir).Append(
+ FilePath(FilePath::StringType(kMockCacheFile)));
+
+ if (command_line.HasSwitch(switches::kRecordMode)) {
+ file_util::CopyFile(url_path, url_path_copy);
+ } else {
+ if (!file_util::ContentsEqual(url_path, url_path_copy)) {
+ std::string contents1, contents2;
+ file_util::ReadFileToString(url_path, &contents1);
+ file_util::ReadFileToString(url_path_copy, &contents2);
+ LOG(ERROR) << "FILE MISMATCH" << contents1 << " VS " << contents2;
+ }
+ EXPECT_TRUE(file_util::ContentsEqual(url_path, url_path_copy));
+ }
+ }
+
+ std::string urls;
+ file_util::ReadFileToString(url_path, &urls);
+
+ std::vector<std::string> url_vector, bad_urls;
+ base::SplitString(urls, '\n', &url_vector);
+ for (std::vector<std::string>::iterator itr = url_vector.begin();
+ itr != url_vector.end(); ++itr) {
+ if (itr->find_first_of("bad") != std::string::npos)
+ bad_urls.push_back(*itr);
+ else
+ visited_urls_.push_back(*itr);
+ }
+
+ if (!bad_urls.empty()) {
+ FilePath url_errors_path = url_path.DirName()
+ .Append(url_path.BaseName().value() +
+ FilePath::StringType(kURLErrorsSuffix));
+ std::string error_content = JoinString(bad_urls, '\n');
+ file_util::WriteFile(url_errors_path, error_content.c_str(),
+ error_content.size());
+ }
+ }
+
+ if (command_line.HasSwitch(switches::kRecordStats))
+ file_util::WriteFile(command_line.GetSwitchValuePath(
+ switches::kRecordStats), kTestStatistics.c_str(),
+ kTestStatistics.size());
+ }
+
+ const CommandLine& GetCommandLine() const {
+ return command_line_;
+ }
+
+ const std::vector<std::string>& GetVisitedURLs() const {
+ return visited_urls_;
+ }
+
+ private:
+ CommandLine command_line_;
+ std::vector<std::string> visited_urls_;
+};
+
+class RecordApiTest : public InProcessBrowserTest {
+ public:
+ // Override SetUpCommandline to specify a dummy user_data_dir, which
+ // should be replaced. Clear record-mode, playback-mode, visit-urls,
+ // record-stats, and load-extension.
+ void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ InProcessBrowserTest::SetUpCommandLine(command_line);
+ std::vector<std::string> remove_switches;
+
+ remove_switches.push_back(switches::kUserDataDir);
+ remove_switches.push_back(switches::kVisitURLs);
+ remove_switches.push_back(switches::kVisitURLsCount);
+ remove_switches.push_back(switches::kPlaybackMode);
+ remove_switches.push_back(switches::kRecordStats);
+ remove_switches.push_back(switches::kLoadExtension);
+ *command_line = RunPageCyclerFunction::RemoveSwitches(*command_line,
+ remove_switches);
+
+ command_line->AppendSwitchPath(switches::kUserDataDir,
+ FilePath(kDummyDirName));
+ // Adding a dummy load-extension switch is rather complex since the
+ // preent design of InProcessBrowserTest requires a *real* extension
+ // for the flag, even if we're just testing its replacement. Opted
+ // to omit this for the sake of simplicity.
+ }
+
+ // Run a capture, using standard URL test list and the specified
+ // user data dir. Return via |out_list| the list of error URLs,
+ // if any, resulting from the capture. And return directly the
+ // CaptureURLsFunction that was used, so that its state may be
+ // queried.
+ scoped_refptr<CaptureURLsFunction> RunCapture(const FilePath& user_data_dir,
+ scoped_ptr<base::ListValue>* out_list) {
+
+ scoped_refptr<CaptureURLsFunction> capture_function(
+ new CaptureURLsFunction(new TestProcessStrategy()));
+
+ std::string escaped_user_data_dir;
+ ReplaceChars(user_data_dir.AsUTF8Unsafe(), "\\", "\\\\",
+ &escaped_user_data_dir);
+
+ out_list->reset(utils::ToList(
+ utils::RunFunctionAndReturnResult(capture_function.get(),
+ base::StringPrintf(kCaptureArgs1, escaped_user_data_dir.c_str()),
+ browser())));
+
+ return capture_function;
+ }
+
+ // Verify that the URL list of good and bad URLs was properly handled.
+ // Needed by several tests.
+ bool VerifyURLHandling(const ListValue* result,
+ const TestProcessStrategy& strategy) {
+
+ // Check that the two bad URLs are returned.
+ base::Value* string_value = NULL;
+ StringValue badURL2("URL 2(bad)"), badURL4("URL 4(bad)");
+
+ EXPECT_TRUE(result->GetSize() == 2);
+ result->Get(0, &string_value);
+ EXPECT_TRUE(base::Value::Equals(string_value, &badURL2));
+ result->Get(1, &string_value);
+ EXPECT_TRUE(base::Value::Equals(string_value, &badURL4));
+
+ // Check that both good URLs were visited.
+ std::string goodURL1("URL 1"), goodURL3("URL 3");
+ EXPECT_TRUE(strategy.GetVisitedURLs()[0].compare(goodURL1) == 0
+ && strategy.GetVisitedURLs()[1].compare(goodURL3) == 0);
+
+ return true;
+ }
+};
+
+
+IN_PROC_BROWSER_TEST_F(RecordApiTest, CheckCapture) {
+ ScopedTempDir user_data_dir;
+ scoped_ptr<base::ListValue> result;
+
+ EXPECT_TRUE(user_data_dir.CreateUniqueTempDir());
+ scoped_refptr<CaptureURLsFunction> capture_URLs_function =
+ RunCapture(user_data_dir.path(), &result);
+
+ // Check that user-data-dir switch has been properly overridden.
+ const TestProcessStrategy &strategy =
+ static_cast<const TestProcessStrategy &>(
+ capture_URLs_function->GetProcessStrategy());
+ const CommandLine& command_line = strategy.GetCommandLine();
+
+ EXPECT_TRUE(command_line.HasSwitch(switches::kUserDataDir));
+ EXPECT_TRUE(command_line.GetSwitchValuePath(switches::kUserDataDir) !=
+ FilePath(kDummyDirName));
+
+ EXPECT_TRUE(VerifyURLHandling(result.get(), strategy));
+}
+
+IN_PROC_BROWSER_TEST_F(RecordApiTest, CheckPlayback) {
+ ScopedTempDir user_data_dir;
+
+ EXPECT_TRUE(user_data_dir.CreateUniqueTempDir());
+
+ // Assume this RunCapture operates w/o error, since it's tested
+ // elsewhere.
+ scoped_ptr<base::ListValue> capture_result;
+ RunCapture(user_data_dir.path(), &capture_result);
+
+ std::string escaped_user_data_dir;
+ ReplaceChars(user_data_dir.path().AsUTF8Unsafe(), "\\", "\\\\",
+ &escaped_user_data_dir);
+
+ scoped_refptr<ReplayURLsFunction> playback_function(new ReplayURLsFunction(
+ new TestProcessStrategy()));
+ scoped_ptr<base::DictionaryValue> result(utils::ToDictionary(
+ utils::RunFunctionAndReturnResult(playback_function,
+ base::StringPrintf(kPlaybackArgs1, escaped_user_data_dir.c_str()),
+ browser())));
+
+ // Check that command line user-data-dir was overridden. (That
+ // it was *consistently* overridden in both capture and replay
+ // is verified elsewhere.
+ const TestProcessStrategy &strategy =
+ static_cast<const TestProcessStrategy &>(
+ playback_function->GetProcessStrategy());
+ const CommandLine& command_line = strategy.GetCommandLine();
+
+ EXPECT_TRUE(command_line.HasSwitch(switches::kUserDataDir));
+ EXPECT_TRUE(command_line.GetSwitchValuePath(switches::kUserDataDir) !=
+ FilePath(kDummyDirName));
+
+ // Check that command line load-extension was overridden.
+ EXPECT_TRUE(command_line.HasSwitch(switches::kLoadExtension) &&
+ command_line.GetSwitchValuePath(switches::kLoadExtension)
+ != FilePath(kDummyDirName));
+
+ // Check that visit-urls-count was set to 2.
+ EXPECT_TRUE(command_line.HasSwitch(switches::kVisitURLsCount) &&
+ command_line.GetSwitchValueASCII(switches::kVisitURLsCount).compare("2")
+ == 0);
+
+ // Check for return value with proper stats.
+ EXPECT_EQ(kTestStatistics, utils::GetString(result.get(), kStatsKey));
+
+ ListValue* errors = NULL;
+ EXPECT_TRUE(result->GetList(kErrorsKey, &errors));
+ EXPECT_TRUE(VerifyURLHandling(errors, strategy));
+}