2 This is an instance of the Unit Test Persistence Lib that will utilize
\r
3 the filesystem that a test application is running from to save a serialized
\r
4 version of the internal test state in case the test needs to quit and restore.
\r
6 Copyright (c) Microsoft Corporation.<BR>
\r
7 Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
\r
8 SPDX-License-Identifier: BSD-2-Clause-Patent
\r
12 #include <Library/UnitTestPersistenceLib.h>
\r
13 #include <Library/BaseLib.h>
\r
14 #include <Library/DebugLib.h>
\r
15 #include <Library/MemoryAllocationLib.h>
\r
16 #include <Library/UefiBootServicesTableLib.h>
\r
17 #include <Library/DevicePathLib.h>
\r
18 #include <Library/ShellLib.h>
\r
19 #include <Protocol/LoadedImage.h>
\r
20 #include <UnitTestFrameworkTypes.h>
\r
22 #define CACHE_FILE_SUFFIX L"_Cache.dat"
\r
25 Generate the device path to the cache file.
\r
27 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
\r
29 @retval !NULL A pointer to the EFI_FILE protocol instance for the filesystem.
\r
30 @retval NULL Filesystem could not be found or an error occurred.
\r
34 EFI_DEVICE_PATH_PROTOCOL *
\r
35 GetCacheFileDevicePath (
\r
36 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle
\r
40 UNIT_TEST_FRAMEWORK *Framework;
\r
41 EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
\r
43 CHAR16 *CacheFilePath;
\r
45 UINTN DirectorySlashOffset;
\r
46 UINTN CacheFilePathLength;
\r
47 EFI_DEVICE_PATH_PROTOCOL *CacheFileDevicePath;
\r
49 Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle;
\r
51 CacheFilePath = NULL;
\r
53 CacheFileDevicePath = NULL;
\r
56 // First, we need to get some information from the loaded image.
\r
58 Status = gBS->HandleProtocol (
\r
60 &gEfiLoadedImageProtocolGuid,
\r
61 (VOID **)&LoadedImage
\r
63 if (EFI_ERROR (Status)) {
\r
64 DEBUG ((DEBUG_WARN, "%a - Failed to locate DevicePath for loaded image. %r\n", __FUNCTION__, Status));
\r
69 // Before we can start, change test name from ASCII to Unicode.
\r
71 CacheFilePathLength = AsciiStrLen (Framework->ShortTitle) + 1;
\r
72 TestName = AllocatePool (CacheFilePathLength * sizeof (CHAR16));
\r
77 AsciiStrToUnicodeStrS (Framework->ShortTitle, TestName, CacheFilePathLength);
\r
80 // Now we should have the device path of the root device and a file path for the rest.
\r
81 // In order to target the directory for the test application, we must process
\r
82 // the file path a little.
\r
84 // NOTE: This may not be necessary... Path processing functions exist...
\r
85 // PathCleanUpDirectories (FileNameCopy);
\r
86 // if (PathRemoveLastItem (FileNameCopy)) {
\r
88 AppPath = ConvertDevicePathToText (LoadedImage->FilePath, TRUE, TRUE); // NOTE: This must be freed.
\r
89 DirectorySlashOffset = StrLen (AppPath);
\r
91 // Make sure we didn't get any weird data.
\r
93 if (DirectorySlashOffset == 0) {
\r
94 DEBUG ((DEBUG_ERROR, "%a - Weird 0-length string when processing app path.\n", __FUNCTION__));
\r
99 // Now that we know we have a decent string, let's take a deeper look.
\r
102 if (AppPath[DirectorySlashOffset] == L'\\') {
\r
106 DirectorySlashOffset--;
\r
107 } while (DirectorySlashOffset > 0);
\r
110 // After that little maneuver, DirectorySlashOffset should be pointing at the last '\' in AppString.
\r
111 // That would be the path to the parent directory that the test app is executing from.
\r
112 // Let's check and make sure that's right.
\r
114 if (AppPath[DirectorySlashOffset] != L'\\') {
\r
115 DEBUG ((DEBUG_ERROR, "%a - Could not find a single directory separator in app path.\n", __FUNCTION__));
\r
120 // Now we know some things, we're ready to produce our output string, I think.
\r
122 CacheFilePathLength = DirectorySlashOffset + 1;
\r
123 CacheFilePathLength += StrLen (TestName);
\r
124 CacheFilePathLength += StrLen (CACHE_FILE_SUFFIX);
\r
125 CacheFilePathLength += 1; // Don't forget the NULL terminator.
\r
126 CacheFilePath = AllocateZeroPool (CacheFilePathLength * sizeof (CHAR16));
\r
127 if (!CacheFilePath) {
\r
132 // Let's produce our final path string, shall we?
\r
134 StrnCpyS (CacheFilePath, CacheFilePathLength, AppPath, DirectorySlashOffset + 1); // Copy the path for the parent directory.
\r
135 StrCatS (CacheFilePath, CacheFilePathLength, TestName); // Copy the base name for the test cache.
\r
136 StrCatS (CacheFilePath, CacheFilePathLength, CACHE_FILE_SUFFIX); // Copy the file suffix.
\r
139 // Finally, try to create the device path for the thing thing.
\r
141 CacheFileDevicePath = FileDevicePath (LoadedImage->DeviceHandle, CacheFilePath);
\r
145 // Free allocated buffers.
\r
147 if (AppPath != NULL) {
\r
148 FreePool (AppPath);
\r
151 if (CacheFilePath != NULL) {
\r
152 FreePool (CacheFilePath);
\r
155 if (TestName != NULL) {
\r
156 FreePool (TestName);
\r
159 return CacheFileDevicePath;
\r
163 Determines whether a persistence cache already exists for
\r
164 the given framework.
\r
166 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
\r
169 @retval FALSE Cache doesn't exist or an error occurred.
\r
175 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle
\r
178 EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
\r
180 SHELL_FILE_HANDLE FileHandle;
\r
183 // NOTE: This devpath is allocated and must be freed.
\r
185 FileDevicePath = GetCacheFileDevicePath (FrameworkHandle);
\r
188 // Check to see whether the file exists. If the file can be opened for
\r
189 // reading, it exists. Otherwise, probably not.
\r
191 Status = ShellOpenFileByDevicePath (
\r
194 EFI_FILE_MODE_READ,
\r
197 if (!EFI_ERROR (Status)) {
\r
198 ShellCloseFile (&FileHandle);
\r
201 if (FileDevicePath != NULL) {
\r
202 FreePool (FileDevicePath);
\r
205 DEBUG ((DEBUG_VERBOSE, "%a - Returning %d\n", __FUNCTION__, !EFI_ERROR (Status)));
\r
207 return (!EFI_ERROR (Status));
\r
211 Will save the data associated with an internal Unit Test Framework
\r
212 state in a manner that can persist a Unit Test Application quit or
\r
213 even a system reboot.
\r
215 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
\r
216 @param[in] SaveData A pointer to the buffer containing the serialized
\r
217 framework internal state.
\r
218 @param[in] SaveStateSize The size of SaveData in bytes.
\r
220 @retval EFI_SUCCESS Data is persisted and the test can be safely quit.
\r
221 @retval Others Data is not persisted and test cannot be resumed upon exit.
\r
226 SaveUnitTestCache (
\r
227 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle,
\r
229 IN UINTN SaveStateSize
\r
232 EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
\r
234 SHELL_FILE_HANDLE FileHandle;
\r
238 // Check the inputs for sanity.
\r
240 if ((FrameworkHandle == NULL) || (SaveData == NULL)) {
\r
241 return EFI_INVALID_PARAMETER;
\r
245 // Determine the path for the cache file.
\r
246 // NOTE: This devpath is allocated and must be freed.
\r
248 FileDevicePath = GetCacheFileDevicePath (FrameworkHandle);
\r
251 // First lets open the file if it exists so we can delete it...This is the work around for truncation
\r
253 Status = ShellOpenFileByDevicePath (
\r
256 (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE),
\r
260 if (!EFI_ERROR (Status)) {
\r
262 // If file handle above was opened it will be closed by the delete.
\r
264 Status = ShellDeleteFile (&FileHandle);
\r
265 if (EFI_ERROR (Status)) {
\r
266 DEBUG ((DEBUG_ERROR, "%a failed to delete file %r\n", __FUNCTION__, Status));
\r
271 // Now that we know the path to the file... let's open it for writing.
\r
273 Status = ShellOpenFileByDevicePath (
\r
276 (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE),
\r
279 if (EFI_ERROR (Status)) {
\r
280 DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __FUNCTION__, Status));
\r
285 // Write the data to the file.
\r
287 WriteCount = SaveStateSize;
\r
288 DEBUG ((DEBUG_INFO, "%a - Writing %d bytes to file...\n", __FUNCTION__, WriteCount));
\r
289 Status = ShellWriteFile (
\r
295 if (EFI_ERROR (Status) || (WriteCount != SaveStateSize)) {
\r
296 DEBUG ((DEBUG_ERROR, "%a - Writing to file failed! %r\n", __FUNCTION__, Status));
\r
298 DEBUG ((DEBUG_INFO, "%a - SUCCESS!\n", __FUNCTION__));
\r
302 // No matter what, we should probably close the file.
\r
304 ShellCloseFile (&FileHandle);
\r
307 if (FileDevicePath != NULL) {
\r
308 FreePool (FileDevicePath);
\r
315 Will retrieve any cached state associated with the given framework.
\r
316 Will allocate a buffer to hold the loaded data.
\r
318 @param[in] FrameworkHandle A pointer to the framework that is being persisted.
\r
319 @param[out] SaveData A pointer pointer that will be updated with the address
\r
320 of the loaded data buffer.
\r
321 @param[out] SaveStateSize Return the size of SaveData in bytes.
\r
323 @retval EFI_SUCCESS Data has been loaded successfully and SaveData is updated
\r
324 with a pointer to the buffer.
\r
325 @retval Others An error has occurred and no data has been loaded. SaveData
\r
331 LoadUnitTestCache (
\r
332 IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle,
\r
333 OUT VOID **SaveData,
\r
334 OUT UINTN *SaveStateSize
\r
338 EFI_DEVICE_PATH_PROTOCOL *FileDevicePath;
\r
339 SHELL_FILE_HANDLE FileHandle;
\r
340 BOOLEAN IsFileOpened;
\r
341 UINT64 LargeFileSize;
\r
345 IsFileOpened = FALSE;
\r
349 // Check the inputs for sanity.
\r
351 if ((FrameworkHandle == NULL) || (SaveData == NULL)) {
\r
352 return EFI_INVALID_PARAMETER;
\r
356 // Determine the path for the cache file.
\r
357 // NOTE: This devpath is allocated and must be freed.
\r
359 FileDevicePath = GetCacheFileDevicePath (FrameworkHandle);
\r
362 // Now that we know the path to the file... let's open it for writing.
\r
364 Status = ShellOpenFileByDevicePath (
\r
367 EFI_FILE_MODE_READ,
\r
370 if (EFI_ERROR (Status)) {
\r
371 DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __FUNCTION__, Status));
\r
374 IsFileOpened = TRUE;
\r
378 // Now that the file is opened, we need to determine how large a buffer we need.
\r
380 Status = ShellGetFileSize (FileHandle, &LargeFileSize);
\r
381 if (EFI_ERROR (Status)) {
\r
382 DEBUG ((DEBUG_ERROR, "%a - Failed to determine file size! %r\n", __FUNCTION__, Status));
\r
387 // Now that we know the size, let's allocated a buffer to hold the contents.
\r
389 FileSize = (UINTN)LargeFileSize; // You know what... if it's too large, this lib don't care.
\r
390 *SaveStateSize = FileSize;
\r
391 Buffer = AllocatePool (FileSize);
\r
392 if (Buffer == NULL) {
\r
393 DEBUG ((DEBUG_ERROR, "%a - Failed to allocate a pool to hold the file contents! %r\n", __FUNCTION__, Status));
\r
394 Status = EFI_OUT_OF_RESOURCES;
\r
399 // Finally, let's read the data.
\r
401 Status = ShellReadFile (FileHandle, &FileSize, Buffer);
\r
402 if (EFI_ERROR (Status)) {
\r
403 DEBUG ((DEBUG_ERROR, "%a - Failed to read the file contents! %r\n", __FUNCTION__, Status));
\r
408 // Free allocated buffers
\r
410 if (FileDevicePath != NULL) {
\r
411 FreePool (FileDevicePath);
\r
414 if (IsFileOpened) {
\r
415 ShellCloseFile (&FileHandle);
\r
419 // If we're returning an error, make sure
\r
420 // the state is sane.
\r
421 if (EFI_ERROR (Status) && (Buffer != NULL)) {
\r
426 *SaveData = Buffer;
\r