|  | // Copyright (c) 2011 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/win/registry.h" | 
|  |  | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <cstring> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "base/win/windows_version.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace base { | 
|  | namespace win { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class RegistryTest : public testing::Test { | 
|  | protected: | 
|  | #if defined(_WIN64) | 
|  | static const REGSAM kNativeViewMask = KEY_WOW64_64KEY; | 
|  | static const REGSAM kRedirectedViewMask = KEY_WOW64_32KEY; | 
|  | #else | 
|  | static const REGSAM kNativeViewMask = KEY_WOW64_32KEY; | 
|  | static const REGSAM kRedirectedViewMask = KEY_WOW64_64KEY; | 
|  | #endif  //  _WIN64 | 
|  |  | 
|  | RegistryTest() {} | 
|  | void SetUp() override { | 
|  | // Create a temporary key. | 
|  | RegKey key(HKEY_CURRENT_USER, L"", KEY_ALL_ACCESS); | 
|  | key.DeleteKey(kRootKey); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_READ)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, kRootKey, KEY_READ)); | 
|  | foo_software_key_ = L"Software\\"; | 
|  | foo_software_key_ += kRootKey; | 
|  | foo_software_key_ += L"\\Foo"; | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | // Clean up the temporary key. | 
|  | RegKey key(HKEY_CURRENT_USER, L"", KEY_SET_VALUE); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey)); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_READ)); | 
|  | } | 
|  |  | 
|  | static bool IsRedirectorPresent() { | 
|  | #if defined(_WIN64) | 
|  | return true; | 
|  | #else | 
|  | return OSInfo::GetInstance()->wow64_status() == OSInfo::WOW64_ENABLED; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | const wchar_t* const kRootKey = L"Base_Registry_Unittest"; | 
|  | std::wstring foo_software_key_; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(RegistryTest); | 
|  | }; | 
|  |  | 
|  | // static | 
|  | const REGSAM RegistryTest::kNativeViewMask; | 
|  | const REGSAM RegistryTest::kRedirectedViewMask; | 
|  |  | 
|  | TEST_F(RegistryTest, ValueTest) { | 
|  | RegKey key; | 
|  |  | 
|  | std::wstring foo_key(kRootKey); | 
|  | foo_key += L"\\Foo"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ)); | 
|  |  | 
|  | { | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ | KEY_SET_VALUE)); | 
|  | ASSERT_TRUE(key.Valid()); | 
|  |  | 
|  | const wchar_t kStringValueName[] = L"StringValue"; | 
|  | const wchar_t kDWORDValueName[] = L"DWORDValue"; | 
|  | const wchar_t kInt64ValueName[] = L"Int64Value"; | 
|  | const wchar_t kStringData[] = L"string data"; | 
|  | const DWORD kDWORDData = 0xdeadbabe; | 
|  | const int64_t kInt64Data = 0xdeadbabedeadbabeLL; | 
|  |  | 
|  | // Test value creation | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kStringValueName, kStringData)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kDWORDValueName, kDWORDData)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kInt64ValueName, &kInt64Data, | 
|  | sizeof(kInt64Data), REG_QWORD)); | 
|  | EXPECT_EQ(3U, key.GetValueCount()); | 
|  | EXPECT_TRUE(key.HasValue(kStringValueName)); | 
|  | EXPECT_TRUE(key.HasValue(kDWORDValueName)); | 
|  | EXPECT_TRUE(key.HasValue(kInt64ValueName)); | 
|  |  | 
|  | // Test Read | 
|  | std::wstring string_value; | 
|  | DWORD dword_value = 0; | 
|  | int64_t int64_value = 0; | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.ReadValue(kStringValueName, &string_value)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.ReadValueDW(kDWORDValueName, &dword_value)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.ReadInt64(kInt64ValueName, &int64_value)); | 
|  | EXPECT_STREQ(kStringData, string_value.c_str()); | 
|  | EXPECT_EQ(kDWORDData, dword_value); | 
|  | EXPECT_EQ(kInt64Data, int64_value); | 
|  |  | 
|  | // Make sure out args are not touched if ReadValue fails | 
|  | const wchar_t* kNonExistent = L"NonExistent"; | 
|  | ASSERT_NE(ERROR_SUCCESS, key.ReadValue(kNonExistent, &string_value)); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.ReadValueDW(kNonExistent, &dword_value)); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.ReadInt64(kNonExistent, &int64_value)); | 
|  | EXPECT_STREQ(kStringData, string_value.c_str()); | 
|  | EXPECT_EQ(kDWORDData, dword_value); | 
|  | EXPECT_EQ(kInt64Data, int64_value); | 
|  |  | 
|  | // Test delete | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kStringValueName)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kDWORDValueName)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteValue(kInt64ValueName)); | 
|  | EXPECT_EQ(0U, key.GetValueCount()); | 
|  | EXPECT_FALSE(key.HasValue(kStringValueName)); | 
|  | EXPECT_FALSE(key.HasValue(kDWORDValueName)); | 
|  | EXPECT_FALSE(key.HasValue(kInt64ValueName)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(RegistryTest, BigValueIteratorTest) { | 
|  | RegKey key; | 
|  | std::wstring foo_key(kRootKey); | 
|  | foo_key += L"\\Foo"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ | KEY_SET_VALUE)); | 
|  | ASSERT_TRUE(key.Valid()); | 
|  |  | 
|  | // Create a test value that is larger than MAX_PATH. | 
|  | std::wstring data(MAX_PATH * 2, L'a'); | 
|  |  | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(data.c_str(), data.c_str())); | 
|  |  | 
|  | RegistryValueIterator iterator(HKEY_CURRENT_USER, foo_key.c_str()); | 
|  | ASSERT_TRUE(iterator.Valid()); | 
|  | EXPECT_STREQ(data.c_str(), iterator.Name()); | 
|  | EXPECT_STREQ(data.c_str(), iterator.Value()); | 
|  | // ValueSize() is in bytes, including NUL. | 
|  | EXPECT_EQ((MAX_PATH * 2 + 1) * sizeof(wchar_t), iterator.ValueSize()); | 
|  | ++iterator; | 
|  | EXPECT_FALSE(iterator.Valid()); | 
|  | } | 
|  |  | 
|  | TEST_F(RegistryTest, TruncatedCharTest) { | 
|  | RegKey key; | 
|  | std::wstring foo_key(kRootKey); | 
|  | foo_key += L"\\Foo"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ | KEY_SET_VALUE)); | 
|  | ASSERT_TRUE(key.Valid()); | 
|  |  | 
|  | const wchar_t kName[] = L"name"; | 
|  | // kData size is not a multiple of sizeof(wchar_t). | 
|  | const uint8_t kData[] = {1, 2, 3, 4, 5}; | 
|  | EXPECT_EQ(5u, arraysize(kData)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kName, kData, | 
|  | arraysize(kData), REG_BINARY)); | 
|  |  | 
|  | RegistryValueIterator iterator(HKEY_CURRENT_USER, foo_key.c_str()); | 
|  | ASSERT_TRUE(iterator.Valid()); | 
|  | EXPECT_STREQ(kName, iterator.Name()); | 
|  | // ValueSize() is in bytes. | 
|  | ASSERT_EQ(arraysize(kData), iterator.ValueSize()); | 
|  | // Value() is NUL terminated. | 
|  | int end = (iterator.ValueSize() + sizeof(wchar_t) - 1) / sizeof(wchar_t); | 
|  | EXPECT_NE(L'\0', iterator.Value()[end-1]); | 
|  | EXPECT_EQ(L'\0', iterator.Value()[end]); | 
|  | EXPECT_EQ(0, std::memcmp(kData, iterator.Value(), arraysize(kData))); | 
|  | ++iterator; | 
|  | EXPECT_FALSE(iterator.Valid()); | 
|  | } | 
|  |  | 
|  | TEST_F(RegistryTest, RecursiveDelete) { | 
|  | RegKey key; | 
|  | // Create kRootKey->Foo | 
|  | //                  \->Bar (TestValue) | 
|  | //                     \->Foo (TestValue) | 
|  | //                        \->Bar | 
|  | //                           \->Foo | 
|  | //                  \->Moo | 
|  | //                  \->Foo | 
|  | // and delete kRootKey->Foo | 
|  | std::wstring foo_key(kRootKey); | 
|  | foo_key += L"\\Foo"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Create(HKEY_CURRENT_USER, foo_key.c_str(), KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Bar", KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(L"TestValue", L"TestData")); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Create(HKEY_CURRENT_USER, foo_key.c_str(), KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Moo", KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Create(HKEY_CURRENT_USER, foo_key.c_str(), KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Foo", KEY_WRITE)); | 
|  | foo_key += L"\\Bar"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_WRITE)); | 
|  | foo_key += L"\\Foo"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Foo", KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(L"TestValue", L"TestData")); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_READ)); | 
|  |  | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_WRITE)); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(L"Bar")); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"Foo")); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"Foo\\Bar\\Foo")); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteEmptyKey(L"Foo\\Bar")); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteEmptyKey(L"Foo\\Foo")); | 
|  |  | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Bar", KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"Foo", KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(L"")); | 
|  | ASSERT_NE(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_READ)); | 
|  |  | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(L"Foo")); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(L"Foo")); | 
|  | ASSERT_NE(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_READ)); | 
|  | } | 
|  |  | 
|  | // This test requires running as an Administrator as it tests redirected | 
|  | // registry writes to HKLM\Software | 
|  | // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384253.aspx | 
|  | // TODO(wfh): flaky test on Vista.  See http://crbug.com/377917 | 
|  | TEST_F(RegistryTest, DISABLED_Wow64RedirectedFromNative) { | 
|  | if (!IsRedirectorPresent()) | 
|  | return; | 
|  |  | 
|  | RegKey key; | 
|  |  | 
|  | // Test redirected key access from non-redirected. | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Create(HKEY_LOCAL_MACHINE, | 
|  | foo_software_key_.c_str(), | 
|  | KEY_WRITE | kRedirectedViewMask)); | 
|  | ASSERT_NE(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), KEY_READ)); | 
|  | ASSERT_NE(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, | 
|  | foo_software_key_.c_str(), | 
|  | KEY_READ | kNativeViewMask)); | 
|  |  | 
|  | // Open the non-redirected view of the parent and try to delete the test key. | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, L"Software", KEY_SET_VALUE)); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, | 
|  | L"Software", | 
|  | KEY_SET_VALUE | kNativeViewMask)); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey)); | 
|  |  | 
|  | // Open the redirected view and delete the key created above. | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, | 
|  | L"Software", | 
|  | KEY_SET_VALUE | kRedirectedViewMask)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey)); | 
|  | } | 
|  |  | 
|  | // Test for the issue found in http://crbug.com/384587 where OpenKey would call | 
|  | // Close() and reset wow64_access_ flag to 0 and cause a NOTREACHED to hit on a | 
|  | // subsequent OpenKey call. | 
|  | TEST_F(RegistryTest, SameWowFlags) { | 
|  | RegKey key; | 
|  |  | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, | 
|  | L"Software", | 
|  | KEY_READ | KEY_WOW64_64KEY)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.OpenKey(L"Microsoft", | 
|  | KEY_READ | KEY_WOW64_64KEY)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.OpenKey(L"Windows", | 
|  | KEY_READ | KEY_WOW64_64KEY)); | 
|  | } | 
|  |  | 
|  | // TODO(wfh): flaky test on Vista.  See http://crbug.com/377917 | 
|  | TEST_F(RegistryTest, DISABLED_Wow64NativeFromRedirected) { | 
|  | if (!IsRedirectorPresent()) | 
|  | return; | 
|  | RegKey key; | 
|  |  | 
|  | // Test non-redirected key access from redirected. | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Create(HKEY_LOCAL_MACHINE, | 
|  | foo_software_key_.c_str(), | 
|  | KEY_WRITE | kNativeViewMask)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, foo_software_key_.c_str(), KEY_READ)); | 
|  | ASSERT_NE(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, | 
|  | foo_software_key_.c_str(), | 
|  | KEY_READ | kRedirectedViewMask)); | 
|  |  | 
|  | // Open the redirected view of the parent and try to delete the test key | 
|  | // from the non-redirected view. | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, | 
|  | L"Software", | 
|  | KEY_SET_VALUE | kRedirectedViewMask)); | 
|  | ASSERT_NE(ERROR_SUCCESS, key.DeleteKey(kRootKey)); | 
|  |  | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_LOCAL_MACHINE, | 
|  | L"Software", | 
|  | KEY_SET_VALUE | kNativeViewMask)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(kRootKey)); | 
|  | } | 
|  |  | 
|  | TEST_F(RegistryTest, OpenSubKey) { | 
|  | RegKey key; | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, | 
|  | kRootKey, | 
|  | KEY_READ | KEY_CREATE_SUB_KEY)); | 
|  |  | 
|  | ASSERT_NE(ERROR_SUCCESS, key.OpenKey(L"foo", KEY_READ)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.CreateKey(L"foo", KEY_READ)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_READ)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.OpenKey(L"foo", KEY_READ)); | 
|  |  | 
|  | std::wstring foo_key(kRootKey); | 
|  | foo_key += L"\\Foo"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, | 
|  | key.Open(HKEY_CURRENT_USER, foo_key.c_str(), KEY_READ)); | 
|  |  | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, kRootKey, KEY_WRITE)); | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(L"foo")); | 
|  | } | 
|  |  | 
|  | class TestChangeDelegate { | 
|  | public: | 
|  | TestChangeDelegate() : called_(false) {} | 
|  | ~TestChangeDelegate() {} | 
|  |  | 
|  | void OnKeyChanged() { | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | called_ = true; | 
|  | } | 
|  |  | 
|  | bool WasCalled() { | 
|  | bool was_called = called_; | 
|  | called_ = false; | 
|  | return was_called; | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool called_; | 
|  | }; | 
|  |  | 
|  | TEST_F(RegistryTest, ChangeCallback) { | 
|  | RegKey key; | 
|  | TestChangeDelegate delegate; | 
|  | MessageLoop message_loop; | 
|  |  | 
|  | std::wstring foo_key(kRootKey); | 
|  | foo_key += L"\\Foo"; | 
|  | ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ)); | 
|  |  | 
|  | ASSERT_TRUE(key.StartWatching(Bind(&TestChangeDelegate::OnKeyChanged, | 
|  | Unretained(&delegate)))); | 
|  | EXPECT_FALSE(delegate.WasCalled()); | 
|  |  | 
|  | // Make some change. | 
|  | RegKey key2; | 
|  | ASSERT_EQ(ERROR_SUCCESS, key2.Open(HKEY_CURRENT_USER, foo_key.c_str(), | 
|  | KEY_READ | KEY_SET_VALUE)); | 
|  | ASSERT_TRUE(key2.Valid()); | 
|  | EXPECT_EQ(ERROR_SUCCESS, key2.WriteValue(L"name", L"data")); | 
|  |  | 
|  | // Allow delivery of the notification. | 
|  | EXPECT_FALSE(delegate.WasCalled()); | 
|  | base::RunLoop().Run(); | 
|  |  | 
|  | ASSERT_TRUE(delegate.WasCalled()); | 
|  | EXPECT_FALSE(delegate.WasCalled()); | 
|  |  | 
|  | ASSERT_TRUE(key.StartWatching(Bind(&TestChangeDelegate::OnKeyChanged, | 
|  | Unretained(&delegate)))); | 
|  |  | 
|  | // Change something else. | 
|  | EXPECT_EQ(ERROR_SUCCESS, key2.WriteValue(L"name2", L"data2")); | 
|  | base::RunLoop().Run(); | 
|  | ASSERT_TRUE(delegate.WasCalled()); | 
|  |  | 
|  | ASSERT_TRUE(key.StartWatching(Bind(&TestChangeDelegate::OnKeyChanged, | 
|  | Unretained(&delegate)))); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_FALSE(delegate.WasCalled()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace win | 
|  | }  // namespace base |