Bug 1684971 - Add tests for the new WDBA cache r=nalexander

Differential Revision: https://phabricator.services.mozilla.com/D102224
This commit is contained in:
Kirk Steuber 2021-01-20 17:17:13 +00:00
Родитель c75575c2c1
Коммит 1ebc9745be
6 изменённых файлов: 387 добавлений и 22 удалений

Просмотреть файл

@ -46,7 +46,6 @@
#endif // !defined(RRF_SUBKEY_WOW6464KEY)
using TelemetryFieldResult = mozilla::WindowsErrorResult<std::string>;
using FilePathResult = mozilla::WindowsErrorResult<std::wstring>;
using BoolResult = mozilla::WindowsErrorResult<bool>;
// This function was copied from the implementation of
@ -114,27 +113,6 @@ static TelemetryFieldResult GetOSLocale() {
return std::string(narrowLocaleName.get());
}
static FilePathResult GenerateUUIDStr() {
UUID uuid;
RPC_STATUS status = UuidCreate(&uuid);
if (status != RPC_S_OK) {
HRESULT hr = MAKE_HRESULT(1, FACILITY_RPC, status);
LOG_ERROR(hr);
return FilePathResult(mozilla::WindowsError::FromHResult(hr));
}
// 39 == length of a UUID string including braces and NUL.
wchar_t guidBuf[39] = {};
if (StringFromGUID2(uuid, guidBuf, 39) != 39) {
LOG_ERROR(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
return FilePathResult(
mozilla::WindowsError::FromWin32Error(ERROR_INSUFFICIENT_BUFFER));
}
// Remove the curly braces.
return std::wstring(guidBuf + 1, guidBuf + 37);
}
static FilePathResult GetPingFilePath(std::wstring& uuid) {
wchar_t* rawAppDataPath;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr,

Просмотреть файл

@ -35,3 +35,24 @@ ULONGLONG SecondsPassedSince(ULONGLONG initialTime,
/ 1000 // To milliseconds
/ 1000; // To seconds
}
FilePathResult GenerateUUIDStr() {
UUID uuid;
RPC_STATUS status = UuidCreate(&uuid);
if (status != RPC_S_OK) {
HRESULT hr = MAKE_HRESULT(1, FACILITY_RPC, status);
LOG_ERROR(hr);
return FilePathResult(mozilla::WindowsError::FromHResult(hr));
}
// 39 == length of a UUID string including braces and NUL.
wchar_t guidBuf[39] = {};
if (StringFromGUID2(uuid, guidBuf, 39) != 39) {
LOG_ERROR(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
return FilePathResult(
mozilla::WindowsError::FromWin32Error(ERROR_INSUFFICIENT_BUFFER));
}
// Remove the curly braces.
return std::wstring(guidBuf + 1, guidBuf + 37);
}

Просмотреть файл

@ -15,4 +15,7 @@ ULONGLONG GetCurrentTimestamp();
// to get the current time rather than using a passed value.
ULONGLONG SecondsPassedSince(ULONGLONG initialTime, ULONGLONG currentTime = 0);
using FilePathResult = mozilla::WindowsErrorResult<std::wstring>;
FilePathResult GenerateUUIDStr();
#endif // __DEFAULT_BROWSER_AGENT_COMMON_H__

Просмотреть файл

@ -69,6 +69,9 @@ DEFINES["IMPL_MFBT"] = True
DEFINES["UNICODE"] = True
DEFINES["_UNICODE"] = True
# If defines are added to this list that are required by the Cache or its
# dependencies (Registry, EventLog, common), tests/gtest/moz.build will need to
# be updated as well.
for var in ("MOZ_APP_BASENAME", "MOZ_APP_DISPLAYNAME", "MOZ_APP_VENDOR"):
DEFINES[var] = '"%s"' % CONFIG[var]
@ -92,5 +95,8 @@ defaultagentini.inputs = [
]
FINAL_TARGET_FILES += ["!defaultagent.ini"]
if CONFIG["ENABLE_TESTS"]:
DIRS += ["tests/gtest"]
with Files("**"):
BUG_COMPONENT = ("Firefox", "Installer")

Просмотреть файл

@ -0,0 +1,317 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include <string>
#include "Cache.h"
#include "common.h"
#include "Registry.h"
#include "mozilla/Result.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WinHeaderOnlyUtils.h"
FilePathResult ToWideString(const char* narrowString) {
int wideLen = MultiByteToWideChar(CP_UTF8, 0, narrowString, -1, nullptr, 0);
if (wideLen == 0) {
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
LOG_ERROR(hr);
return mozilla::Err(mozilla::WindowsError::FromHResult(hr));
}
mozilla::UniquePtr<wchar_t[]> wideValue =
mozilla::MakeUnique<wchar_t[]>(wideLen);
int charsWritten = MultiByteToWideChar(CP_UTF8, 0, narrowString, -1,
wideValue.get(), wideLen);
if (charsWritten == 0) {
HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
LOG_ERROR(hr);
return mozilla::Err(mozilla::WindowsError::FromHResult(hr));
}
return std::wstring(wideValue.get());
}
class WDBACacheTest : public ::testing::Test {
protected:
std::wstring mCacheRegKey;
void SetUp() override {
// Create a unique registry key to put the cache in for each test.
const ::testing::TestInfo* const testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
FilePathResult testCaseResult = ToWideString(testInfo->test_case_name());
ASSERT_TRUE(testCaseResult.isOk());
mCacheRegKey = testCaseResult.unwrap();
FilePathResult testNameResult = ToWideString(testInfo->name());
ASSERT_TRUE(testNameResult.isOk());
mCacheRegKey += L'.';
mCacheRegKey += testNameResult.unwrap();
FilePathResult uuidResult = GenerateUUIDStr();
ASSERT_TRUE(uuidResult.isOk());
mCacheRegKey += L'.';
mCacheRegKey += uuidResult.unwrap();
}
void TearDown() override {
// It seems like the TearDown probably doesn't run if SetUp doesn't
// succeed, but I can't find any documentation saying that. And we don't
// want to accidentally clobber the entirety of AGENT_REGKEY_NAME.
if (!mCacheRegKey.empty()) {
std::wstring regKey = AGENT_REGKEY_NAME;
regKey += L'\\';
regKey += mCacheRegKey;
RegDeleteTreeW(HKEY_CURRENT_USER, regKey.c_str());
}
}
};
TEST_F(WDBACacheTest, BasicFunctionality) {
Cache cache(mCacheRegKey.c_str());
VoidResult result = cache.Init();
ASSERT_TRUE(result.isOk());
// Test that the cache starts empty
Cache::MaybeEntryResult entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
Cache::MaybeEntry entry = entryResult.unwrap();
ASSERT_TRUE(entry.isNothing());
// Test that the cache stops accepting items when it is full.
ASSERT_EQ(Cache::kDefaultCapacity, 2);
Cache::Entry toWrite = Cache::Entry{
.notificationType = "string1",
.notificationShown = "string2",
.notificationAction = "string3",
.prevNotificationAction = "string4",
};
result = cache.Enqueue(toWrite);
ASSERT_TRUE(result.isOk());
toWrite = Cache::Entry{
.notificationType = "string5",
.notificationShown = "string6",
.notificationAction = "string7",
.prevNotificationAction = "string8",
};
result = cache.Enqueue(toWrite);
ASSERT_TRUE(result.isOk());
toWrite = Cache::Entry{
.notificationType = "string9",
.notificationShown = "string10",
.notificationAction = "string11",
.prevNotificationAction = "string12",
};
result = cache.Enqueue(toWrite);
ASSERT_TRUE(result.isErr());
// Read the two cache entries back out and test that they match the expected
// values.
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 2);
ASSERT_EQ(entry.value().notificationType, "string1");
ASSERT_EQ(entry.value().notificationShown, "string2");
ASSERT_EQ(entry.value().notificationAction, "string3");
ASSERT_TRUE(entry.value().prevNotificationAction.isSome());
ASSERT_EQ(entry.value().prevNotificationAction.value(), "string4");
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 2);
ASSERT_EQ(entry.value().notificationType, "string5");
ASSERT_EQ(entry.value().notificationShown, "string6");
ASSERT_EQ(entry.value().notificationAction, "string7");
ASSERT_TRUE(entry.value().prevNotificationAction.isSome());
ASSERT_EQ(entry.value().prevNotificationAction.value(), "string8");
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isNothing());
}
TEST_F(WDBACacheTest, Version1Migration) {
// Set up 2 version 1 cache entries
VoidResult result = RegistrySetValueString(
IsPrefixed::Unprefixed, L"PingCacheNotificationType0", "string1");
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
L"PingCacheNotificationShown0", "string2");
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
L"PingCacheNotificationAction0", "string3");
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
L"PingCacheNotificationType1", "string4");
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
L"PingCacheNotificationShown1", "string5");
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
L"PingCacheNotificationAction1", "string6");
ASSERT_TRUE(result.isOk());
Cache cache(mCacheRegKey.c_str());
result = cache.Init();
ASSERT_TRUE(result.isOk());
Cache::MaybeEntryResult entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
Cache::MaybeEntry entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 1);
ASSERT_EQ(entry.value().notificationType, "string1");
ASSERT_EQ(entry.value().notificationShown, "string2");
ASSERT_EQ(entry.value().notificationAction, "string3");
ASSERT_TRUE(entry.value().prevNotificationAction.isNothing());
// Insert a new item to test coexistence of different versions
Cache::Entry toWrite = Cache::Entry{
.notificationType = "string7",
.notificationShown = "string8",
.notificationAction = "string9",
.prevNotificationAction = "string10",
};
result = cache.Enqueue(toWrite);
ASSERT_TRUE(result.isOk());
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 1);
ASSERT_EQ(entry.value().notificationType, "string4");
ASSERT_EQ(entry.value().notificationShown, "string5");
ASSERT_EQ(entry.value().notificationAction, "string6");
ASSERT_TRUE(entry.value().prevNotificationAction.isNothing());
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 2);
ASSERT_EQ(entry.value().notificationType, "string7");
ASSERT_EQ(entry.value().notificationShown, "string8");
ASSERT_EQ(entry.value().notificationAction, "string9");
ASSERT_TRUE(entry.value().prevNotificationAction.isSome());
ASSERT_EQ(entry.value().prevNotificationAction.value(), "string10");
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isNothing());
}
TEST_F(WDBACacheTest, ForwardsCompatibility) {
// Set up a cache that might have been made by a future version with a larger
// capacity set and more keys per entry.
std::wstring settingsKey = mCacheRegKey + L"\\version2";
VoidResult result = RegistrySetValueDword(
IsPrefixed::Unprefixed, Cache::kCapacityRegName, 8, settingsKey.c_str());
ASSERT_TRUE(result.isOk());
// We're going to insert the future version's entry at index 6 so there's
// space for 1 more before we loop back to index 0. Then we are going to
// enqueue 2 new values to test that this works properly.
result = RegistrySetValueDword(IsPrefixed::Unprefixed, Cache::kFrontRegName,
6, settingsKey.c_str());
ASSERT_TRUE(result.isOk());
result = RegistrySetValueDword(IsPrefixed::Unprefixed, Cache::kSizeRegName, 1,
settingsKey.c_str());
ASSERT_TRUE(result.isOk());
// Insert an entry as if it was inserted by a future version
std::wstring entryRegKey = settingsKey + L"\\6";
result =
RegistrySetValueDword(IsPrefixed::Unprefixed, Cache::kEntryVersionKey,
9999, entryRegKey.c_str());
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
Cache::kNotificationTypeKey, "string1",
entryRegKey.c_str());
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
Cache::kNotificationShownKey, "string2",
entryRegKey.c_str());
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
Cache::kNotificationActionKey, "string3",
entryRegKey.c_str());
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed,
Cache::kPrevNotificationActionKey, "string4",
entryRegKey.c_str());
ASSERT_TRUE(result.isOk());
result = RegistrySetValueString(IsPrefixed::Unprefixed, L"UnknownFutureKey",
"string5", entryRegKey.c_str());
ASSERT_TRUE(result.isOk());
Cache cache(mCacheRegKey.c_str());
result = cache.Init();
ASSERT_TRUE(result.isOk());
// Insert 2 new items to test that these features work with a different
// capacity.
Cache::Entry toWrite = Cache::Entry{
.notificationType = "string6",
.notificationShown = "string7",
.notificationAction = "string8",
.prevNotificationAction = "string9",
};
result = cache.Enqueue(toWrite);
ASSERT_TRUE(result.isOk());
toWrite = Cache::Entry{
.notificationType = "string10",
.notificationShown = "string11",
.notificationAction = "string12",
.prevNotificationAction = "string13",
};
result = cache.Enqueue(toWrite);
ASSERT_TRUE(result.isOk());
// Read cache and verify the output
Cache::MaybeEntryResult entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
Cache::MaybeEntry entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 9999);
ASSERT_EQ(entry.value().notificationType, "string1");
ASSERT_EQ(entry.value().notificationShown, "string2");
ASSERT_EQ(entry.value().notificationAction, "string3");
ASSERT_TRUE(entry.value().prevNotificationAction.isSome());
ASSERT_EQ(entry.value().prevNotificationAction.value(), "string4");
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 2);
ASSERT_EQ(entry.value().notificationType, "string6");
ASSERT_EQ(entry.value().notificationShown, "string7");
ASSERT_EQ(entry.value().notificationAction, "string8");
ASSERT_TRUE(entry.value().prevNotificationAction.isSome());
ASSERT_EQ(entry.value().prevNotificationAction.value(), "string9");
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isSome());
ASSERT_EQ(entry.value().entryVersion, 2);
ASSERT_EQ(entry.value().notificationType, "string10");
ASSERT_EQ(entry.value().notificationShown, "string11");
ASSERT_EQ(entry.value().notificationAction, "string12");
ASSERT_TRUE(entry.value().prevNotificationAction.isSome());
ASSERT_EQ(entry.value().prevNotificationAction.value(), "string13");
entryResult = cache.Dequeue();
ASSERT_TRUE(entryResult.isOk());
entry = entryResult.unwrap();
ASSERT_TRUE(entry.isNothing());
}

Просмотреть файл

@ -0,0 +1,40 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
Library("DefaultAgentTest")
# Normally, we only include the test code in gtest sources because they get
# linked against libxul. But the code we are testing is not part of libxul, so
# if we want it to be available to us to test, we have to include it here.
UNIFIED_SOURCES += [
"../../Cache.cpp",
"../../common.cpp",
"../../EventLog.cpp",
"../../Registry.cpp",
"CacheTest.cpp",
]
LOCAL_INCLUDES += [
"/toolkit/mozapps/defaultagent",
]
OS_LIBS += [
"advapi32",
"kernel32",
"rpcrt4",
]
DEFINES["UNICODE"] = True
DEFINES["_UNICODE"] = True
for var in ("MOZ_APP_BASENAME", "MOZ_APP_DISPLAYNAME", "MOZ_APP_VENDOR"):
DEFINES[var] = '"%s"' % CONFIG[var]
# We need STL headers that aren't allowed when wrapping is on (at least
# <filesystem>, and possibly others).
DisableStlWrapping()
FINAL_LIBRARY = "xul-gtest"