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