diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-28 03:03:43 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-28 03:03:43 +0000 |
commit | 6f5366ac2dba6f208b5c2546b4f2c1a93bd4fbb1 (patch) | |
tree | 1985f6c9956989d5273e1c59a423723d4e37ca6c /chrome/browser/sync | |
parent | ed45566a74d2a8dae55ab4e2589ce5f0d78e49b2 (diff) | |
download | chromium_src-6f5366ac2dba6f208b5c2546b4f2c1a93bd4fbb1.zip chromium_src-6f5366ac2dba6f208b5c2546b4f2c1a93bd4fbb1.tar.gz chromium_src-6f5366ac2dba6f208b5c2546b4f2c1a93bd4fbb1.tar.bz2 |
Take 2 at browser_sync::ExtensionsActivityMonitor.
Original: http://codereview.chromium.org/325001/show
TEST=ExtensionsActivityMonitorTest
Review URL: http://codereview.chromium.org/333041
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30313 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync')
-rw-r--r-- | chrome/browser/sync/engine/build_commit_command.cc | 15 | ||||
-rw-r--r-- | chrome/browser/sync/engine/build_commit_command.h | 2 | ||||
-rw-r--r-- | chrome/browser/sync/engine/process_commit_response_command.cc | 10 | ||||
-rw-r--r-- | chrome/browser/sync/engine/process_commit_response_command.h | 9 | ||||
-rw-r--r-- | chrome/browser/sync/engine/syncer.cc | 27 | ||||
-rw-r--r-- | chrome/browser/sync/engine/syncer.h | 5 | ||||
-rw-r--r-- | chrome/browser/sync/engine/syncer_session.h | 12 | ||||
-rw-r--r-- | chrome/browser/sync/protocol/sync.proto | 21 | ||||
-rw-r--r-- | chrome/browser/sync/util/extensions_activity_monitor.cc | 99 | ||||
-rw-r--r-- | chrome/browser/sync/util/extensions_activity_monitor.h | 71 | ||||
-rw-r--r-- | chrome/browser/sync/util/extensions_activity_monitor_unittest.cc | 242 |
11 files changed, 506 insertions, 7 deletions
diff --git a/chrome/browser/sync/engine/build_commit_command.cc b/chrome/browser/sync/engine/build_commit_command.cc index 46200c6..5058bba 100644 --- a/chrome/browser/sync/engine/build_commit_command.cc +++ b/chrome/browser/sync/engine/build_commit_command.cc @@ -30,6 +30,20 @@ namespace browser_sync { BuildCommitCommand::BuildCommitCommand() {} BuildCommitCommand::~BuildCommitCommand() {} +void BuildCommitCommand::AddExtensionsActivityToMessage( + SyncerSession* session, CommitMessage* message) { + const ExtensionsActivityMonitor::Records& records = + session->extensions_activity(); + for (ExtensionsActivityMonitor::Records::const_iterator it = records.begin(); + it != records.end(); ++it) { + sync_pb::CommitMessage_ChromiumExtensionsActivity* activity_message = + message->add_extensions_activity(); + activity_message->set_extension_id(it->second.extension_id); + activity_message->set_bookmark_writes_since_last_commit( + it->second.bookmark_write_count); + } +} + void BuildCommitCommand::ExecuteImpl(SyncerSession* session) { ClientToServerMessage message; message.set_share(ToUTF8(session->account_name()).get_string()); @@ -38,6 +52,7 @@ void BuildCommitCommand::ExecuteImpl(SyncerSession* session) { CommitMessage* commit_message = message.mutable_commit(); commit_message->set_cache_guid( session->write_transaction()->directory()->cache_guid()); + AddExtensionsActivityToMessage(session, commit_message); const vector<Id>& commit_ids = session->commit_ids(); for (size_t i = 0; i < commit_ids.size(); i++) { diff --git a/chrome/browser/sync/engine/build_commit_command.h b/chrome/browser/sync/engine/build_commit_command.h index 445024f..1c29590 100644 --- a/chrome/browser/sync/engine/build_commit_command.h +++ b/chrome/browser/sync/engine/build_commit_command.h @@ -19,6 +19,8 @@ class BuildCommitCommand : public SyncerCommand { virtual void ExecuteImpl(SyncerSession *session); private: + void AddExtensionsActivityToMessage(SyncerSession* session, + CommitMessage* message); DISALLOW_COPY_AND_ASSIGN(BuildCommitCommand); }; diff --git a/chrome/browser/sync/engine/process_commit_response_command.cc b/chrome/browser/sync/engine/process_commit_response_command.cc index 3deec6d..72fd0e6 100644 --- a/chrome/browser/sync/engine/process_commit_response_command.cc +++ b/chrome/browser/sync/engine/process_commit_response_command.cc @@ -52,11 +52,19 @@ void ResetErrorCounters(SyncerStatus status) { status.zero_consecutive_errors(); } -ProcessCommitResponseCommand::ProcessCommitResponseCommand() {} +ProcessCommitResponseCommand::ProcessCommitResponseCommand( + ExtensionsActivityMonitor* monitor) : extensions_monitor_(monitor) {} ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {} void ProcessCommitResponseCommand::ModelChangingExecuteImpl( SyncerSession* session) { + ProcessCommitResponse(session); + if (!session->HadSuccessfulCommits()) + extensions_monitor_->PutRecords(session->extensions_activity()); +} + +void ProcessCommitResponseCommand::ProcessCommitResponse( + SyncerSession* session) { // TODO(sync): This function returns if it sees problems. We probably want // to flag the need for an update or similar. ScopedDirLookup dir(session->dirman(), session->account_name()); diff --git a/chrome/browser/sync/engine/process_commit_response_command.h b/chrome/browser/sync/engine/process_commit_response_command.h index 4e7c94b..9950ba4 100644 --- a/chrome/browser/sync/engine/process_commit_response_command.h +++ b/chrome/browser/sync/engine/process_commit_response_command.h @@ -23,7 +23,7 @@ namespace browser_sync { class ProcessCommitResponseCommand : public ModelChangingSyncerCommand { public: - ProcessCommitResponseCommand(); + explicit ProcessCommitResponseCommand(ExtensionsActivityMonitor* monitor); virtual ~ProcessCommitResponseCommand(); virtual void ModelChangingExecuteImpl(SyncerSession* session); @@ -36,6 +36,9 @@ class ProcessCommitResponseCommand : public ModelChangingSyncerCommand { std::set<syncable::Id>* deleted_folders, SyncerSession* const session); + // Actually does the work of execute. + void ProcessCommitResponse(SyncerSession* session); + void ProcessSuccessfulCommitResponse(syncable::WriteTransaction* trans, const CommitResponse_EntryResponse& server_entry, const syncable::Id& pre_commit_id, syncable::MutableEntry* local_entry, @@ -47,6 +50,10 @@ class ProcessCommitResponseCommand : public ModelChangingSyncerCommand { const CommitResponse_EntryResponse& server_entry, syncable::MutableEntry* local_entry); + // We may need to update this with records from a commit attempt if the + // attempt failed. + ExtensionsActivityMonitor* extensions_monitor_; + DISALLOW_COPY_AND_ASSIGN(ProcessCommitResponseCommand); }; diff --git a/chrome/browser/sync/engine/syncer.cc b/chrome/browser/sync/engine/syncer.cc index de8b1b2..ee56bb9 100644 --- a/chrome/browser/sync/engine/syncer.cc +++ b/chrome/browser/sync/engine/syncer.cc @@ -5,6 +5,8 @@ #include "chrome/browser/sync/engine/syncer.h" #include "base/format_macros.h" +#include "base/message_loop.h" +#include "chrome/browser/chrome_thread.h" #include "chrome/browser/sync/engine/apply_updates_command.h" #include "chrome/browser/sync/engine/build_and_process_conflict_sets_command.h" #include "chrome/browser/sync/engine/build_commit_command.h" @@ -66,12 +68,21 @@ Syncer::Syncer( syncer_event_channel_.reset(new SyncerEventChannel(shutdown)); shutdown_channel_.reset(new ShutdownChannel(this)); + extensions_monitor_ = new ExtensionsActivityMonitor(); + ScopedDirLookup dir(dirman_, account_name_); // The directory must be good here. CHECK(dir.good()); } -Syncer::~Syncer() {} +Syncer::~Syncer() { + if (!ChromeThread::DeleteSoon(ChromeThread::UI, FROM_HERE, + extensions_monitor_)) { + // In unittests, there may be no UI thread, so the above will fail. + delete extensions_monitor_; + } + extensions_monitor_ = NULL; +} void Syncer::RequestNudge(int milliseconds) { SyncerEvent event; @@ -92,6 +103,17 @@ bool Syncer::SyncShare(SyncProcessState* process_state) { SyncerSession session(&cycle_state, process_state); session.set_source(TestAndSetUpdatesSource()); session.set_notifications_enabled(notifications_enabled()); + // This isn't perfect, as we can end up bundling extensions activity + // intended for the next session into the current one. We could do a + // test-and-reset as with the source, but note that also falls short if + // the commit request fails (due to lost connection, for example), as we will + // fall all the way back to the syncer thread main loop in that case, and + // wind up creating a new session when a connection is established, losing + // the records set here on the original attempt. This should provide us + // with the right data "most of the time", and we're only using this for + // analysis purposes, so Law of Large Numbers FTW. + extensions_monitor_->GetAndClearRecords( + session.mutable_extensions_activity()); SyncShare(&session, SYNCER_BEGIN, SYNCER_END); return session.HasMoreToSync(); } @@ -205,7 +227,8 @@ void Syncer::SyncShare(SyncerSession* session, } case PROCESS_COMMIT_RESPONSE: { LOG(INFO) << "Processing the commit response"; - ProcessCommitResponseCommand process_response_command; + ProcessCommitResponseCommand process_response_command( + extensions_monitor_); process_response_command.Execute(session); next_step = BUILD_AND_PROCESS_CONFLICT_SETS; break; diff --git a/chrome/browser/sync/engine/syncer.h b/chrome/browser/sync/engine/syncer.h index 2d6ba6a..067193b 100644 --- a/chrome/browser/sync/engine/syncer.h +++ b/chrome/browser/sync/engine/syncer.h @@ -19,6 +19,7 @@ #include "chrome/browser/sync/syncable/directory_event.h" #include "chrome/browser/sync/util/event_sys-inl.h" #include "chrome/browser/sync/util/event_sys.h" +#include "chrome/browser/sync/util/extensions_activity_monitor.h" #include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST namespace syncable { @@ -180,6 +181,10 @@ class Syncer { // by the SyncerThread that created us. ModelSafeWorker* model_safe_worker_; + // We use this to stuff extensions activity into CommitMessages so the server + // can correlate commit traffic with extension-related bookmark mutations. + ExtensionsActivityMonitor* extensions_monitor_; + // The source of the last nudge. sync_pb::GetUpdatesCallerInfo::GET_UPDATES_SOURCE updates_source_; diff --git a/chrome/browser/sync/engine/syncer_session.h b/chrome/browser/sync/engine/syncer_session.h index 83643d7..5e4710b 100644 --- a/chrome/browser/sync/engine/syncer_session.h +++ b/chrome/browser/sync/engine/syncer_session.h @@ -22,6 +22,7 @@ #include "chrome/browser/sync/engine/syncer_types.h" #include "chrome/browser/sync/engine/syncproto.h" #include "chrome/browser/sync/util/event_sys.h" +#include "chrome/browser/sync/util/extensions_activity_monitor.h" #include "chrome/browser/sync/util/sync_types.h" #include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST @@ -307,6 +308,14 @@ class SyncerSession { source_ = source; } + const ExtensionsActivityMonitor::Records& extensions_activity() const { + return extensions_activity_; + } + + ExtensionsActivityMonitor::Records* mutable_extensions_activity() { + return &extensions_activity_; + } + bool notifications_enabled() const { return notifications_enabled_; } @@ -355,6 +364,9 @@ class SyncerSession { // The source for initiating this syncer session. sync_pb::GetUpdatesCallerInfo::GET_UPDATES_SOURCE source_; + // Information about extensions activity since the last successful commit. + ExtensionsActivityMonitor::Records extensions_activity_; + // True if notifications are enabled when this session was created. bool notifications_enabled_; diff --git a/chrome/browser/sync/protocol/sync.proto b/chrome/browser/sync/protocol/sync.proto index c9b50cc..ad7f61f 100644 --- a/chrome/browser/sync/protocol/sync.proto +++ b/chrome/browser/sync/protocol/sync.proto @@ -172,6 +172,22 @@ message CommitMessage { // A GUID that identifies the committing sync client. This value will be // returned as originator_cache_guid for any new items. optional string cache_guid = 2; + + // This message contains diagnostic information used to correlate + // commit-related traffic with extensions-related mutations to the + // data models in chromium. It plays no functional role in + // processing this CommitMessage. + message ChromiumExtensionsActivity { + // The human-readable ID identifying the extension responsible + // for the traffic reported in this ChromiumExtensionsActivity. + optional string extension_id = 1; + + // How many times the extension successfully invoked a write + // operation through the bookmarks API since the last CommitMessage. + optional uint32 bookmark_writes_since_last_commit = 2; + } + + repeated ChromiumExtensionsActivity extensions_activity = 3; }; message GetUpdatesCallerInfo { @@ -203,7 +219,7 @@ message AuthenticateMessage { message ClientToServerMessage { required string share = 1; - optional int32 protocol_version = 2 [default = 21]; + optional int32 protocol_version = 2 [default = 22]; enum CONTENTS { COMMIT = 1; GET_UPDATES = 2; @@ -243,8 +259,7 @@ message CommitResponse { optional string parent_id_string = 4; // This value is the same as the position_in_parent value returned within - // the SyncEntity message in GetUpdatesResponse. It is returned if the - // item was assigned a new position. + // the SyncEntity message in GetUpdatesResponse. optional int64 position_in_parent = 5; // The item's current version. diff --git a/chrome/browser/sync/util/extensions_activity_monitor.cc b/chrome/browser/sync/util/extensions_activity_monitor.cc new file mode 100644 index 0000000..bb94d07 --- /dev/null +++ b/chrome/browser/sync/util/extensions_activity_monitor.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2009 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/sync/util/extensions_activity_monitor.h" + +#include "base/task.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_bookmarks_module.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/notification_service.h" + +namespace browser_sync { + +namespace { +// A helper task to register an ExtensionsActivityMonitor as an observer of +// events on the UI thread (even though the monitor may live on another thread). +// This liberates ExtensionsActivityMonitor from having to be ref counted. +class RegistrationTask : public Task { + public: + RegistrationTask(ExtensionsActivityMonitor* monitor, + NotificationRegistrar* registrar) + : monitor_(monitor), registrar_(registrar) {} + virtual ~RegistrationTask() {} + + virtual void Run() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + // It would be nice if we could specify a Source for each specific function + // we wanted to observe, but the actual function objects are allocated on + // the fly so there is no reliable object to point to (same problem if we + // wanted to use the string name). Thus, we use all sources and filter in + // Observe. + registrar_->Add(monitor_, NotificationType::EXTENSION_BOOKMARKS_API_INVOKED, + NotificationService::AllSources()); + } + + private: + ExtensionsActivityMonitor* monitor_; + NotificationRegistrar* registrar_; + DISALLOW_COPY_AND_ASSIGN(RegistrationTask); +}; +} // namespace + +ExtensionsActivityMonitor::ExtensionsActivityMonitor() { + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + new RegistrationTask(this, ®istrar_)); +} + +ExtensionsActivityMonitor::~ExtensionsActivityMonitor() { + // In some unrelated unit tests, there is no running UI loop. In this case, + // the PostTask in our ctor will not result in anything running, so |this| + // won't be used for anything. In this case (or whenever no registration took + // place) and only this case we allow destruction on another loop, but this + // isn't something a client of this class can control; it happens implicitly + // by not having a running UI thread. + if (!registrar_.IsEmpty()) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + // The registrar calls RemoveAll in its dtor (which would happen in a + // moment but explicitly call this so it is clear why we need to be on the + // ui_loop_. + registrar_.RemoveAll(); + } +} + +void ExtensionsActivityMonitor::GetAndClearRecords(Records* buffer) { + AutoLock lock(records_lock_); + buffer->clear(); + buffer->swap(records_); +} + +void ExtensionsActivityMonitor::PutRecords(const Records& records) { + AutoLock lock(records_lock_); + for (Records::const_iterator i = records.begin(); i != records.end(); ++i) { + records_[i->first].extension_id = i->second.extension_id; + records_[i->first].bookmark_write_count += i->second.bookmark_write_count; + } +} + +void ExtensionsActivityMonitor::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + AutoLock lock(records_lock_); + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + const Extension* extension = Source<const Extension>(source).ptr(); + const BookmarksFunction* f = Details<const BookmarksFunction>(details).ptr(); + if (f->name() == "bookmarks.update" || + f->name() == "bookmarks.move" || + f->name() == "bookmarks.create" || + f->name() == "bookmarks.removeTree" || + f->name() == "bookmarks.remove") { + Record& record = records_[extension->id()]; + record.extension_id = extension->id(); + record.bookmark_write_count++; + } +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/util/extensions_activity_monitor.h b/chrome/browser/sync/util/extensions_activity_monitor.h new file mode 100644 index 0000000..9f5a1a3 --- /dev/null +++ b/chrome/browser/sync/util/extensions_activity_monitor.h @@ -0,0 +1,71 @@ +// Copyright (c) 2009 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_SYNC_UTIL_EXTENSIONS_ACTIVITY_MONITOR_H_ +#define CHROME_BROWSER_SYNC_UTIL_EXTENSIONS_ACTIVITY_MONITOR_H_ + +#include "base/lock.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +namespace browser_sync { + +// A class to monitor usage of extensions APIs to send to sync servers, with +// the ability to purge data once sync servers have acknowledged it (successful +// commit response). +// +// This can be used from any thread (it is a 'monitor' in the synchronization +// sense as well), HOWEVER +// +// *** IT MUST BE DELETED FROM THE UI LOOP *** +// +// Consider using MessageLoop::DeleteSoon. (Yes, this means if you allocate +// an ExtensionsActivityMonitor on a thread other than UI, you must 'new' it). +class ExtensionsActivityMonitor : public NotificationObserver { + public: + // A data record of activity performed by extension |extension_id|. + struct Record { + Record() : bookmark_write_count(0U) {} + + // The human-readable ID identifying the extension responsible + // for the activity reported in this Record. + std::string extension_id; + + // How many times the extension successfully invoked a write + // operation through the bookmarks API since the last CommitMessage. + uint32 bookmark_write_count; + }; + + typedef std::map<std::string, Record> Records; + + // Creates an ExtensionsActivityMonitor to monitor extensions activities on + // ChromeThread::UI. + ExtensionsActivityMonitor(); + ~ExtensionsActivityMonitor(); + + // Fills |buffer| with snapshot of current records in constant time by + // swapping. This is done mutually exclusively w.r.t methods of this class. + void GetAndClearRecords(Records* buffer); + + // Add |records| piece-wise (by extension id) to the set of active records. + // This is done mutually exclusively w.r.t the methods of this class. + void PutRecords(const Records& records); + + // NotificationObserver implementation. Called on |ui_loop_|. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + private: + Records records_; + mutable Lock records_lock_; + + // Used only from UI loop. + NotificationRegistrar registrar_; +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_UTIL_EXTENSIONS_ACTIVITY_MONITOR_H_ diff --git a/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc new file mode 100644 index 0000000..9d18830 --- /dev/null +++ b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2009 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 entry. + +#include "chrome/browser/sync/util/extensions_activity_monitor.h" + +#include "base/file_path.h" +#include "base/string_util.h" +#include "base/waitable_event.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_bookmarks_module.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/notification_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +using browser_sync::ExtensionsActivityMonitor; +namespace keys = extension_manifest_keys; + +namespace { + +const char* kTestExtensionPath1 = "c:\\testextension1"; +const char* kTestExtensionPath2 = "c:\\testextension2"; +const char* kTestExtensionVersion = "1.0.0.0"; +const char* kTestExtensionName = "foo extension"; + +template <class FunctionType> +class BookmarkAPIEventTask : public Task { + public: + BookmarkAPIEventTask(FunctionType* t, Extension* e, size_t repeats, + base::WaitableEvent* done) : + extension_(e), function_(t), repeats_(repeats), done_(done) {} + virtual void Run() { + for (size_t i = 0; i < repeats_; i++) { + NotificationService::current()->Notify( + NotificationType::EXTENSION_BOOKMARKS_API_INVOKED, + Source<Extension>(extension_.get()), + Details<const BookmarksFunction>(function_.get())); + } + done_->Signal(); + } + private: + scoped_ptr<Extension> extension_; + scoped_refptr<FunctionType> function_; + size_t repeats_; + base::WaitableEvent* done_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkAPIEventTask); +}; + +class BookmarkAPIEventGenerator { + public: + BookmarkAPIEventGenerator() {} + virtual ~BookmarkAPIEventGenerator() {} + template <class T> + void NewEvent(const std::string& extension_path, + T* bookmarks_function, size_t repeats) { +#if defined(OS_WIN) + FilePath path(UTF8ToWide(extension_path)); +#elif defined(OS_POSIX) + FilePath path(extension_path); +#endif + Extension* extension = new Extension(path); + std::string error; + DictionaryValue input; + input.SetString(keys::kVersion, kTestExtensionVersion); + input.SetString(keys::kName, kTestExtensionName); + extension->InitFromValue(input, false, &error); + bookmarks_function->set_name(T::function_name()); + base::WaitableEvent done_event(false, false); + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + new BookmarkAPIEventTask<T>(bookmarks_function, extension, + repeats, &done_event)); + done_event.Wait(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BookmarkAPIEventGenerator); +}; +} // namespace + +class DoUIThreadSetupTask : public Task { + public: + DoUIThreadSetupTask(NotificationService** service, + base::WaitableEvent* done) + : service_(service), signal_when_done_(done) {} + virtual ~DoUIThreadSetupTask() {} + virtual void Run() { + *service_ = new NotificationService(); + signal_when_done_->Signal(); + } + private: + NotificationService** service_; + base::WaitableEvent* signal_when_done_; + DISALLOW_COPY_AND_ASSIGN(DoUIThreadSetupTask); +}; + +class ExtensionsActivityMonitorTest : public testing::Test { + public: + ExtensionsActivityMonitorTest() : service_(NULL), + ui_thread_(ChromeThread::UI) { } + virtual ~ExtensionsActivityMonitorTest() {} + + virtual void SetUp() { + ui_thread_.Start(); + base::WaitableEvent service_created(false, false); + ui_thread_.message_loop()->PostTask(FROM_HERE, + new DoUIThreadSetupTask(&service_, &service_created)); + service_created.Wait(); + } + + virtual void TearDown() { + ui_thread_.message_loop()->DeleteSoon(FROM_HERE, service_); + ui_thread_.Stop(); + } + + MessageLoop* ui_loop() { return ui_thread_.message_loop(); } + + static std::string GetExtensionIdForPath(const std::string& extension_path) { + std::string error; +#if defined(OS_WIN) + FilePath path(UTF8ToWide(extension_path)); +#elif defined(OS_POSIX) + FilePath path(extension_path); +#endif + Extension e(path); + DictionaryValue input; + input.SetString(keys::kVersion, kTestExtensionVersion); + input.SetString(keys::kName, kTestExtensionName); + e.InitFromValue(input, false, &error); + EXPECT_EQ("", error); + return e.id(); + } + private: + NotificationService* service_; + ChromeThread ui_thread_; +}; + +TEST_F(ExtensionsActivityMonitorTest, Basic) { + ExtensionsActivityMonitor* monitor = new ExtensionsActivityMonitor(); + BookmarkAPIEventGenerator generator; + + generator.NewEvent<RemoveBookmarkFunction>(kTestExtensionPath1, + new RemoveBookmarkFunction(), 1); + generator.NewEvent<MoveBookmarkFunction>(kTestExtensionPath1, + new MoveBookmarkFunction(), 1); + generator.NewEvent<UpdateBookmarkFunction>(kTestExtensionPath1, + new UpdateBookmarkFunction(), 2); + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 3); + generator.NewEvent<SearchBookmarksFunction>(kTestExtensionPath1, + new SearchBookmarksFunction(), 5); + const uint32 writes_by_extension1 = 1 + 1 + 2 + 3; + + generator.NewEvent<RemoveTreeBookmarkFunction>(kTestExtensionPath2, + new RemoveTreeBookmarkFunction(), 8); + generator.NewEvent<GetBookmarkTreeFunction>(kTestExtensionPath2, + new GetBookmarkTreeFunction(), 13); + generator.NewEvent<GetBookmarkChildrenFunction>(kTestExtensionPath2, + new GetBookmarkChildrenFunction(), 21); + generator.NewEvent<GetBookmarksFunction>(kTestExtensionPath2, + new GetBookmarksFunction(), 33); + const uint32 writes_by_extension2 = 8; + + ExtensionsActivityMonitor::Records results; + monitor->GetAndClearRecords(&results); + + std::string id1 = GetExtensionIdForPath(kTestExtensionPath1); + std::string id2 = GetExtensionIdForPath(kTestExtensionPath2); + + EXPECT_EQ(2U, results.size()); + EXPECT_TRUE(results.end() != results.find(id1)); + EXPECT_TRUE(results.end() != results.find(id2)); + EXPECT_EQ(writes_by_extension1, results[id1].bookmark_write_count); + EXPECT_EQ(writes_by_extension2, results[id2].bookmark_write_count); + + ui_loop()->DeleteSoon(FROM_HERE, monitor); +} + +TEST_F(ExtensionsActivityMonitorTest, Put) { + ExtensionsActivityMonitor* monitor = new ExtensionsActivityMonitor(); + BookmarkAPIEventGenerator generator; + std::string id1 = GetExtensionIdForPath(kTestExtensionPath1); + std::string id2 = GetExtensionIdForPath(kTestExtensionPath2); + + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 5); + generator.NewEvent<MoveBookmarkFunction>(kTestExtensionPath2, + new MoveBookmarkFunction(), 8); + + ExtensionsActivityMonitor::Records results; + monitor->GetAndClearRecords(&results); + + EXPECT_EQ(2U, results.size()); + EXPECT_EQ(5U, results[id1].bookmark_write_count); + EXPECT_EQ(8U, results[id2].bookmark_write_count); + + generator.NewEvent<GetBookmarksFunction>(kTestExtensionPath2, + new GetBookmarksFunction(), 3); + generator.NewEvent<UpdateBookmarkFunction>(kTestExtensionPath2, + new UpdateBookmarkFunction(), 2); + + // Simulate a commit failure, which augments the active record set with the + // refugee records. + monitor->PutRecords(results); + ExtensionsActivityMonitor::Records new_records; + monitor->GetAndClearRecords(&new_records); + + EXPECT_EQ(2U, results.size()); + EXPECT_EQ(id1, new_records[id1].extension_id); + EXPECT_EQ(id2, new_records[id2].extension_id); + EXPECT_EQ(5U, new_records[id1].bookmark_write_count); + EXPECT_EQ(8U + 2U, new_records[id2].bookmark_write_count); + ui_loop()->DeleteSoon(FROM_HERE, monitor); +} + +TEST_F(ExtensionsActivityMonitorTest, MultiGet) { + ExtensionsActivityMonitor* monitor = new ExtensionsActivityMonitor(); + BookmarkAPIEventGenerator generator; + std::string id1 = GetExtensionIdForPath(kTestExtensionPath1); + + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 5); + + ExtensionsActivityMonitor::Records results; + monitor->GetAndClearRecords(&results); + + EXPECT_EQ(1U, results.size()); + EXPECT_EQ(5U, results[id1].bookmark_write_count); + + monitor->GetAndClearRecords(&results); + EXPECT_TRUE(results.empty()); + + generator.NewEvent<CreateBookmarkFunction>(kTestExtensionPath1, + new CreateBookmarkFunction(), 3); + monitor->GetAndClearRecords(&results); + + EXPECT_EQ(1U, results.size()); + EXPECT_EQ(3U, results[id1].bookmark_write_count); + + ui_loop()->DeleteSoon(FROM_HERE, monitor); +} |