TestCommon.cpp (13539B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestCommon.h" 5 #include "TestHooks.h" 6 #include <winget/GroupPolicy.h> 7 #include <winget/UserSettings.h> 8 #include <AppInstallerMsixInfo.h> 9 #include <AppInstallerDownloader.h> 10 11 using namespace AppInstaller; 12 13 namespace TestCommon 14 { 15 namespace 16 { 17 int initRand() 18 { 19 srand(static_cast<unsigned int>(time(NULL))); 20 return rand(); 21 }; 22 23 inline int getRand() 24 { 25 static int randStart = initRand(); 26 return randStart++; 27 } 28 29 inline std::filesystem::path GetFilePath(std::filesystem::path path, const std::string& baseName, const std::string& baseExt) 30 { 31 path /= baseName + std::to_string(getRand()) + baseExt; 32 return path; 33 } 34 35 inline std::filesystem::path GetTempFilePath(const std::string& baseName, const std::string& baseExt) 36 { 37 std::filesystem::path tempFilePath = std::filesystem::temp_directory_path(); 38 return GetFilePath(tempFilePath, baseName, baseExt); 39 } 40 41 static TempFileDestructionBehavior s_TempFileDestructorBehavior = TempFileDestructionBehavior::Delete; 42 static std::vector<std::filesystem::path> s_TempFilesOnFile; 43 44 static std::filesystem::path s_TestDataFileBasePath{}; 45 46 bool CleanVolatileTestRoot(HKEY root) 47 { 48 THROW_IF_WIN32_ERROR(RegDeleteTreeW(root, nullptr)); 49 return true; 50 } 51 } 52 53 TempFile::TempFile(const std::string& baseName, const std::string& baseExt, std::optional<KeepTempFile> keepTempFile) 54 { 55 _filepath = GetTempFilePath(baseName, baseExt); 56 if (!keepTempFile) 57 { 58 std::filesystem::remove(_filepath); 59 } 60 } 61 62 TempFile::TempFile(const std::filesystem::path& parent, const std::string& baseName, const std::string& baseExt, std::optional<KeepTempFile> keepTempFile) 63 { 64 _filepath = GetFilePath(parent, baseName, baseExt); 65 if (!keepTempFile) 66 { 67 std::filesystem::remove(_filepath); 68 } 69 } 70 71 TempFile::TempFile(const std::filesystem::path& filePath, std::optional<KeepTempFile> keepTempFile) 72 { 73 if (filePath.is_relative()) 74 { 75 _filepath = std::filesystem::temp_directory_path(); 76 _filepath /= filePath; 77 } 78 else 79 { 80 _filepath = filePath; 81 } 82 if (!keepTempFile) 83 { 84 std::filesystem::remove(_filepath); 85 } 86 } 87 88 TempFile::~TempFile() try 89 { 90 if (m_destructionToken) 91 { 92 switch (s_TempFileDestructorBehavior) 93 { 94 case TempFileDestructionBehavior::Delete: 95 std::filesystem::remove_all(_filepath); 96 break; 97 case TempFileDestructionBehavior::Keep: 98 break; 99 case TempFileDestructionBehavior::ShellExecuteOnFailure: 100 s_TempFilesOnFile.emplace_back(std::move(_filepath)); 101 break; 102 } 103 } 104 } 105 CATCH_LOG_RETURN() 106 107 void TempFile::Rename(const std::filesystem::path& newFilePath) 108 { 109 std::filesystem::rename(GetPath(), newFilePath); 110 _filepath = newFilePath; 111 } 112 113 void TempFile::Release() 114 { 115 m_destructionToken = false; 116 } 117 118 void TempFile::SetDestructorBehavior(TempFileDestructionBehavior behavior) 119 { 120 s_TempFileDestructorBehavior = behavior; 121 } 122 123 void TempFile::SetTestFailed(bool failed) 124 { 125 if (failed) 126 { 127 for (const auto& path : s_TempFilesOnFile) 128 { 129 SHELLEXECUTEINFOW seinfo{}; 130 seinfo.cbSize = sizeof(seinfo); 131 seinfo.lpVerb = L"open"; 132 seinfo.lpFile = path.c_str(); 133 134 ShellExecuteExW(&seinfo); 135 } 136 } 137 else 138 { 139 s_TempFilesOnFile.clear(); 140 } 141 } 142 143 TempDirectory::TempDirectory(const std::string& baseName, bool create) 144 { 145 _filepath = GetTempFilePath(baseName, ""); 146 if (create) 147 { 148 if (std::filesystem::exists(_filepath)) 149 { 150 std::filesystem::remove_all(_filepath); 151 } 152 std::filesystem::create_directories(_filepath); 153 } 154 } 155 156 std::filesystem::path TestDataFile::GetPath() const 157 { 158 std::filesystem::path result = s_TestDataFileBasePath; 159 result /= m_path; 160 return result; 161 } 162 163 void TestDataFile::SetTestDataBasePath(const std::filesystem::path& path) 164 { 165 s_TestDataFileBasePath = path; 166 } 167 168 void TestProgress::OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) 169 { 170 if (m_OnProgress) 171 { 172 m_OnProgress(current, maximum, type); 173 } 174 } 175 176 void TestProgress::SetProgressMessage(std::string_view) 177 { 178 } 179 180 void TestProgress::BeginProgress() 181 { 182 } 183 184 void TestProgress::EndProgress(bool) 185 { 186 } 187 188 bool TestProgress::IsCancelledBy(AppInstaller::CancelReason) 189 { 190 return false; 191 } 192 193 AppInstaller::IProgressCallback::CancelFunctionRemoval TestProgress::SetCancellationFunction(std::function<void()>&&) 194 { 195 return {}; 196 } 197 198 wil::unique_hkey RegCreateVolatileTestRoot() 199 { 200 // First create/open the real test root 201 wil::unique_hkey root; 202 THROW_IF_WIN32_ERROR(RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\WinGet\\TestRoot", 0, nullptr, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, nullptr, &root, nullptr)); 203 204 static bool s_ignored = CleanVolatileTestRoot(root.get()); 205 206 // Create a random name 207 GUID name{}; 208 (void)CoCreateGuid(&name); 209 210 wchar_t nameBuffer[256]; 211 (void)StringFromGUID2(name, nameBuffer, ARRAYSIZE(nameBuffer)); 212 213 return RegCreateVolatileSubKey(root.get(), nameBuffer); 214 } 215 216 wil::unique_hkey RegCreateVolatileSubKey(HKEY parent, const std::wstring& name) 217 { 218 wil::unique_hkey result; 219 THROW_IF_WIN32_ERROR(RegCreateKeyExW(parent, name.c_str(), 0, nullptr, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, nullptr, &result, nullptr)); 220 return result; 221 } 222 223 void SetRegistryValue(HKEY key, const std::wstring& name, const std::wstring& value, DWORD type) 224 { 225 THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, type, reinterpret_cast<const BYTE*>(value.c_str()), static_cast<DWORD>(sizeof(wchar_t) * (value.size() + 1)))); 226 } 227 228 void SetRegistryValue(HKEY key, const std::wstring& name, const std::vector<BYTE>& value, DWORD type) 229 { 230 THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, type, reinterpret_cast<const BYTE*>(value.data()), static_cast<DWORD>(value.size()))); 231 } 232 233 void SetRegistryValue(HKEY key, const std::wstring& name, DWORD value) 234 { 235 THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(DWORD))); 236 } 237 238 void EnableDevMode(bool enable) 239 { 240 wil::unique_hkey result; 241 THROW_IF_WIN32_ERROR(RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, KEY_ALL_ACCESS|KEY_WOW64_64KEY, &result)); 242 SetRegistryValue(result.get(), L"AllowDevelopmentWithoutDevLicense", (enable ? 1 : 0)); 243 } 244 245 TestUserSettings::TestUserSettings(bool keepFileSettings) 246 { 247 if (!keepFileSettings) 248 { 249 m_settings.clear(); 250 } 251 252 AppInstaller::Settings::SetUserSettingsOverride(this); 253 } 254 255 TestUserSettings::~TestUserSettings() 256 { 257 AppInstaller::Settings::SetUserSettingsOverride(nullptr); 258 } 259 260 std::unique_ptr<TestUserSettings> TestUserSettings::EnableExperimentalFeature(Settings::ExperimentalFeature::Feature feature, bool keepFileSettings) 261 { 262 std::unique_ptr<TestUserSettings> result = std::make_unique<TestUserSettings>(keepFileSettings); 263 264 // Due to the template usage, this needs to be updated for any features that want to use it. 265 // Currently no feature is used. Uncomment below when a feature needs to be used. 266 // switch (feature) 267 // { 268 // default: 269 // THROW_HR(E_NOTIMPL); 270 // } 271 UNREFERENCED_PARAMETER(feature); 272 273 return result; 274 } 275 276 bool InstallCertFromSignedPackage(const std::filesystem::path& package) 277 { 278 auto [certContext, certStore] = AppInstaller::Msix::GetCertContextFromMsix(package); 279 280 wil::unique_hcertstore trustedPeopleStore; 281 trustedPeopleStore.reset(CertOpenStore( 282 CERT_STORE_PROV_SYSTEM_W, 283 PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 284 NULL, 285 CERT_SYSTEM_STORE_LOCAL_MACHINE, 286 L"TrustedPeople")); 287 THROW_LAST_ERROR_IF(!trustedPeopleStore.get()); 288 289 wil::unique_cert_context existingCert; 290 existingCert.reset(CertFindCertificateInStore( 291 trustedPeopleStore.get(), 292 PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 293 0, 294 CERT_FIND_EXISTING, 295 certContext.get(), 296 nullptr)); 297 298 // Add if it does not already exist in the store 299 if (!existingCert.get()) 300 { 301 THROW_LAST_ERROR_IF(!CertAddCertificateContextToStore( 302 trustedPeopleStore.get(), 303 certContext.get(), 304 CERT_STORE_ADD_NEW, 305 nullptr)); 306 307 return true; 308 } 309 310 return false; 311 } 312 313 bool UninstallCertFromSignedPackage(const std::filesystem::path& package) 314 { 315 auto [certContext, certStore] = AppInstaller::Msix::GetCertContextFromMsix(package); 316 317 wil::unique_hcertstore trustedPeopleStore; 318 trustedPeopleStore.reset(CertOpenStore( 319 CERT_STORE_PROV_SYSTEM_W, 320 PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 321 NULL, 322 CERT_SYSTEM_STORE_LOCAL_MACHINE, 323 L"TrustedPeople")); 324 THROW_LAST_ERROR_IF(!trustedPeopleStore.get()); 325 326 wil::unique_cert_context existingCert; 327 existingCert.reset(CertFindCertificateInStore( 328 trustedPeopleStore.get(), 329 PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 330 0, 331 CERT_FIND_EXISTING, 332 certContext.get(), 333 nullptr)); 334 335 // Remove if it exists in the store 336 if (existingCert.get()) 337 { 338 THROW_LAST_ERROR_IF(!CertDeleteCertificateFromStore(existingCert.get())); 339 340 return true; 341 } 342 343 return false; 344 } 345 346 bool GetMsixPackageManifestReader(std::string_view testFileName, IAppxManifestReader** manifestReader) 347 { 348 // Locate test file 349 TestDataFile testFile(testFileName); 350 auto path = testFile.GetPath().u8string(); 351 352 // Get the stream for the test file 353 auto stream = AppInstaller::Utility::GetReadOnlyStreamFromURI(path); 354 355 // Get manifest from package reader 356 Microsoft::WRL::ComPtr<IAppxPackageReader> packageReader; 357 return AppInstaller::Msix::GetPackageReader(stream.Get(), &packageReader) 358 && SUCCEEDED(packageReader->GetManifest(manifestReader)); 359 } 360 361 std::string RemoveConsoleFormat(const std::string& str) 362 { 363 // We are looking something that starts with "\x1b[0m" 364 if (!str.empty() && str[0] == '\x1b') 365 { 366 // Find first m 367 auto pos = str.find("m"); 368 if (pos != std::string::npos) 369 { 370 return str.substr(pos + 1); 371 } 372 } 373 374 return str; 375 } 376 377 Json::Value ConvertToJson(const std::string& content) 378 { 379 auto contentClean = RemoveConsoleFormat(content); 380 381 Json::Value root; 382 Json::CharReaderBuilder builder; 383 const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); 384 std::string error; 385 386 if (!reader->parse(contentClean.c_str(), contentClean.c_str() + contentClean.size(), &root, &error)) 387 { 388 throw error; 389 } 390 391 return root; 392 } 393 394 void SetTestPathOverrides() 395 { 396 // Force all tests to run against settings inside this container. 397 // This prevents test runs from trashing the users actual settings. 398 Runtime::TestHook_SetPathOverride(Runtime::PathName::LocalState, Runtime::GetPathTo(Runtime::PathName::LocalState) / "Tests"); 399 Runtime::TestHook_SetPathOverride(Runtime::PathName::UserFileSettings, Runtime::GetPathTo(Runtime::PathName::UserFileSettings) / "Tests"); 400 Runtime::TestHook_SetPathOverride(Runtime::PathName::StandardSettings, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "Tests"); 401 Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForRead, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "WinGet_SecureSettings_Tests"); 402 Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForWrite, Runtime::GetPathDetailsFor(Runtime::PathName::SecureSettingsForRead)); 403 } 404 }