mirror of https://github.com/acidanthera/audk.git
511 lines
15 KiB
C
511 lines
15 KiB
C
/** @file
|
|
This is an instance of the Unit Test Persistence Lib that will utilize
|
|
the filesystem that a test application is running from to save a serialized
|
|
version of the internal test state in case the test needs to quit and restore.
|
|
|
|
Copyright (c) Microsoft Corporation.<BR>
|
|
Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
**/
|
|
|
|
#include <PiDxe.h>
|
|
#include <Library/UnitTestPersistenceLib.h>
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
#include <Library/DevicePathLib.h>
|
|
#include <Library/ShellLib.h>
|
|
#include <Library/UefiLib.h>
|
|
#include <Protocol/LoadedImage.h>
|
|
#include <UnitTestFrameworkTypes.h>
|
|
|
|
#define CACHE_FILE_SUFFIX L"_Cache.dat"
|
|
|
|
CHAR16 *mCachePath = NULL;
|
|
|
|
/**
|
|
Generate the file name and path to the cache file.
|
|
|
|
@param[in] FrameworkHandle A pointer to the framework that is being persisted.
|
|
|
|
@retval !NULL A pointer to the EFI_FILE protocol instance for the filesystem.
|
|
@retval NULL Filesystem could not be found or an error occurred.
|
|
|
|
**/
|
|
STATIC
|
|
CHAR16 *
|
|
GetCacheFileName (
|
|
IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UNIT_TEST_FRAMEWORK *Framework;
|
|
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
|
|
CHAR16 *AppPath;
|
|
CHAR16 *CacheFilePath;
|
|
CHAR16 *TestName;
|
|
UINTN DirectorySlashOffset;
|
|
UINTN CacheFilePathLength;
|
|
|
|
Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle;
|
|
AppPath = NULL;
|
|
CacheFilePath = NULL;
|
|
TestName = NULL;
|
|
|
|
//
|
|
// First, we need to get some information from the loaded image.
|
|
//
|
|
Status = gBS->HandleProtocol (
|
|
gImageHandle,
|
|
&gEfiLoadedImageProtocolGuid,
|
|
(VOID **)&LoadedImage
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_WARN, "%a - Failed to locate DevicePath for loaded image. %r\n", __func__, Status));
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Before we can start, change test name from ASCII to Unicode.
|
|
//
|
|
CacheFilePathLength = AsciiStrLen (Framework->ShortTitle) + 1;
|
|
TestName = AllocatePool (CacheFilePathLength * sizeof (CHAR16));
|
|
if (!TestName) {
|
|
goto Exit;
|
|
}
|
|
|
|
AsciiStrToUnicodeStrS (Framework->ShortTitle, TestName, CacheFilePathLength);
|
|
|
|
//
|
|
// Now we should have the device path of the root device and a file path for the rest.
|
|
// In order to target the directory for the test application, we must process
|
|
// the file path a little.
|
|
//
|
|
// NOTE: This may not be necessary... Path processing functions exist...
|
|
// PathCleanUpDirectories (FileNameCopy);
|
|
// if (PathRemoveLastItem (FileNameCopy)) {
|
|
//
|
|
if (mCachePath == NULL) {
|
|
AppPath = ConvertDevicePathToText (LoadedImage->FilePath, TRUE, TRUE); // NOTE: This must be freed.
|
|
if (AppPath == NULL) {
|
|
goto Exit;
|
|
}
|
|
|
|
DirectorySlashOffset = StrLen (AppPath);
|
|
//
|
|
// Make sure we didn't get any weird data.
|
|
//
|
|
if (DirectorySlashOffset == 0) {
|
|
DEBUG ((DEBUG_ERROR, "%a - Weird 0-length string when processing app path.\n", __func__));
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Now that we know we have a decent string, let's take a deeper look.
|
|
//
|
|
do {
|
|
if (AppPath[DirectorySlashOffset] == L'\\') {
|
|
break;
|
|
}
|
|
|
|
DirectorySlashOffset--;
|
|
} while (DirectorySlashOffset > 0);
|
|
|
|
//
|
|
// After that little maneuver, DirectorySlashOffset should be pointing at the last '\' in AppString.
|
|
// That would be the path to the parent directory that the test app is executing from.
|
|
// Let's check and make sure that's right.
|
|
//
|
|
if (AppPath[DirectorySlashOffset] != L'\\') {
|
|
DEBUG ((DEBUG_ERROR, "%a - Could not find a single directory separator in app path.\n", __func__));
|
|
goto Exit;
|
|
}
|
|
} else {
|
|
AppPath = FullyQualifyPath (mCachePath); // NOTE: This must be freed.
|
|
if (AppPath == NULL) {
|
|
goto Exit;
|
|
}
|
|
|
|
DirectorySlashOffset = StrLen (AppPath);
|
|
|
|
if (AppPath[DirectorySlashOffset - 1] != L'\\') {
|
|
// Set the slash if user did not specify it on the newly allocated pool
|
|
AppPath = ReallocatePool (
|
|
(DirectorySlashOffset + 1) * sizeof (CHAR16),
|
|
(DirectorySlashOffset + 2) * sizeof (CHAR16),
|
|
AppPath
|
|
);
|
|
AppPath[DirectorySlashOffset] = L'\\';
|
|
AppPath[DirectorySlashOffset + 1] = L'\0';
|
|
} else {
|
|
// Otherwise the user input is good enough to go, mostly
|
|
DirectorySlashOffset--;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now we know some things, we're ready to produce our output string, I think.
|
|
//
|
|
CacheFilePathLength = DirectorySlashOffset + 1;
|
|
CacheFilePathLength += StrLen (TestName);
|
|
CacheFilePathLength += StrLen (CACHE_FILE_SUFFIX);
|
|
CacheFilePathLength += 1; // Don't forget the NULL terminator.
|
|
CacheFilePath = AllocateZeroPool (CacheFilePathLength * sizeof (CHAR16));
|
|
if (!CacheFilePath) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Let's produce our final path string, shall we?
|
|
//
|
|
StrnCpyS (CacheFilePath, CacheFilePathLength, AppPath, DirectorySlashOffset + 1); // Copy the path for the parent directory.
|
|
StrCatS (CacheFilePath, CacheFilePathLength, TestName); // Copy the base name for the test cache.
|
|
StrCatS (CacheFilePath, CacheFilePathLength, CACHE_FILE_SUFFIX); // Copy the file suffix.
|
|
|
|
Exit:
|
|
//
|
|
// Free allocated buffers.
|
|
//
|
|
if (AppPath != NULL) {
|
|
FreePool (AppPath);
|
|
}
|
|
|
|
if (TestName != NULL) {
|
|
FreePool (TestName);
|
|
}
|
|
|
|
return CacheFilePath;
|
|
}
|
|
|
|
/**
|
|
Determines whether a persistence cache already exists for
|
|
the given framework.
|
|
|
|
@param[in] FrameworkHandle A pointer to the framework that is being persisted.
|
|
|
|
@retval TRUE
|
|
@retval FALSE Cache doesn't exist or an error occurred.
|
|
|
|
**/
|
|
BOOLEAN
|
|
EFIAPI
|
|
DoesCacheExist (
|
|
IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle
|
|
)
|
|
{
|
|
CHAR16 *FileName;
|
|
EFI_STATUS Status;
|
|
SHELL_FILE_HANDLE FileHandle;
|
|
|
|
//
|
|
// NOTE: This devpath is allocated and must be freed.
|
|
//
|
|
FileName = GetCacheFileName (FrameworkHandle);
|
|
if (FileName == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check to see whether the file exists. If the file can be opened for
|
|
// reading, it exists. Otherwise, probably not.
|
|
//
|
|
Status = ShellOpenFileByName (
|
|
FileName,
|
|
&FileHandle,
|
|
EFI_FILE_MODE_READ,
|
|
0
|
|
);
|
|
if (!EFI_ERROR (Status)) {
|
|
ShellCloseFile (&FileHandle);
|
|
}
|
|
|
|
if (FileName != NULL) {
|
|
FreePool (FileName);
|
|
}
|
|
|
|
DEBUG ((DEBUG_VERBOSE, "%a - Returning %d\n", __func__, !EFI_ERROR (Status)));
|
|
|
|
return (!EFI_ERROR (Status));
|
|
}
|
|
|
|
/**
|
|
Will save the data associated with an internal Unit Test Framework
|
|
state in a manner that can persist a Unit Test Application quit or
|
|
even a system reboot.
|
|
|
|
@param[in] FrameworkHandle A pointer to the framework that is being persisted.
|
|
@param[in] SaveData A pointer to the buffer containing the serialized
|
|
framework internal state.
|
|
@param[in] SaveStateSize The size of SaveData in bytes.
|
|
|
|
@retval EFI_SUCCESS Data is persisted and the test can be safely quit.
|
|
@retval Others Data is not persisted and test cannot be resumed upon exit.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
SaveUnitTestCache (
|
|
IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle,
|
|
IN VOID *SaveData,
|
|
IN UINTN SaveStateSize
|
|
)
|
|
{
|
|
CHAR16 *FileName;
|
|
EFI_STATUS Status;
|
|
SHELL_FILE_HANDLE FileHandle;
|
|
UINTN WriteCount;
|
|
|
|
//
|
|
// Check the inputs for sanity.
|
|
//
|
|
if ((FrameworkHandle == NULL) || (SaveData == NULL)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Determine the path for the cache file.
|
|
// NOTE: This devpath is allocated and must be freed.
|
|
//
|
|
FileName = GetCacheFileName (FrameworkHandle);
|
|
if (FileName == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// First lets open the file if it exists so we can delete it...This is the work around for truncation
|
|
//
|
|
Status = ShellOpenFileByName (
|
|
FileName,
|
|
&FileHandle,
|
|
(EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE),
|
|
0
|
|
);
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
//
|
|
// If file handle above was opened it will be closed by the delete.
|
|
//
|
|
Status = ShellDeleteFile (&FileHandle);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "%a failed to delete file %r\n", __func__, Status));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now that we know the path to the file... let's open it for writing.
|
|
//
|
|
Status = ShellOpenFileByName (
|
|
FileName,
|
|
&FileHandle,
|
|
(EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE),
|
|
0
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __func__, Status));
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Write the data to the file.
|
|
//
|
|
WriteCount = SaveStateSize;
|
|
DEBUG ((DEBUG_INFO, "%a - Writing %d bytes to file...\n", __func__, WriteCount));
|
|
Status = ShellWriteFile (
|
|
FileHandle,
|
|
&WriteCount,
|
|
SaveData
|
|
);
|
|
|
|
if (EFI_ERROR (Status) || (WriteCount != SaveStateSize)) {
|
|
DEBUG ((DEBUG_ERROR, "%a - Writing to file failed! %r\n", __func__, Status));
|
|
} else {
|
|
DEBUG ((DEBUG_INFO, "%a - SUCCESS!\n", __func__));
|
|
}
|
|
|
|
//
|
|
// No matter what, we should probably close the file.
|
|
//
|
|
ShellCloseFile (&FileHandle);
|
|
|
|
Exit:
|
|
if (FileName != NULL) {
|
|
FreePool (FileName);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Will retrieve any cached state associated with the given framework.
|
|
Will allocate a buffer to hold the loaded data.
|
|
|
|
@param[in] FrameworkHandle A pointer to the framework that is being persisted.
|
|
@param[out] SaveData A pointer pointer that will be updated with the address
|
|
of the loaded data buffer.
|
|
@param[out] SaveStateSize Return the size of SaveData in bytes.
|
|
|
|
@retval EFI_SUCCESS Data has been loaded successfully and SaveData is updated
|
|
with a pointer to the buffer.
|
|
@retval Others An error has occurred and no data has been loaded. SaveData
|
|
is set to NULL.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
LoadUnitTestCache (
|
|
IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle,
|
|
OUT VOID **SaveData,
|
|
OUT UINTN *SaveStateSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CHAR16 *FileName;
|
|
SHELL_FILE_HANDLE FileHandle;
|
|
BOOLEAN IsFileOpened;
|
|
UINT64 LargeFileSize;
|
|
UINTN FileSize;
|
|
VOID *Buffer;
|
|
|
|
IsFileOpened = FALSE;
|
|
Buffer = NULL;
|
|
|
|
//
|
|
// Check the inputs for sanity.
|
|
//
|
|
if ((FrameworkHandle == NULL) || (SaveData == NULL)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Determine the path for the cache file.
|
|
// NOTE: This devpath is allocated and must be freed.
|
|
//
|
|
FileName = GetCacheFileName (FrameworkHandle);
|
|
if (FileName == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Now that we know the path to the file... let's open it for writing.
|
|
//
|
|
Status = ShellOpenFileByName (
|
|
FileName,
|
|
&FileHandle,
|
|
EFI_FILE_MODE_READ,
|
|
0
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __func__, Status));
|
|
goto Exit;
|
|
} else {
|
|
IsFileOpened = TRUE;
|
|
}
|
|
|
|
//
|
|
// Now that the file is opened, we need to determine how large a buffer we need.
|
|
//
|
|
Status = ShellGetFileSize (FileHandle, &LargeFileSize);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "%a - Failed to determine file size! %r\n", __func__, Status));
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Now that we know the size, let's allocated a buffer to hold the contents.
|
|
//
|
|
FileSize = (UINTN)LargeFileSize; // You know what... if it's too large, this lib don't care.
|
|
*SaveStateSize = FileSize;
|
|
Buffer = AllocatePool (FileSize);
|
|
if (Buffer == NULL) {
|
|
DEBUG ((DEBUG_ERROR, "%a - Failed to allocate a pool to hold the file contents! %r\n", __func__, Status));
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Finally, let's read the data.
|
|
//
|
|
Status = ShellReadFile (FileHandle, &FileSize, Buffer);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "%a - Failed to read the file contents! %r\n", __func__, Status));
|
|
}
|
|
|
|
Exit:
|
|
//
|
|
// Free allocated buffers
|
|
//
|
|
if (FileName != NULL) {
|
|
FreePool (FileName);
|
|
}
|
|
|
|
if (IsFileOpened) {
|
|
ShellCloseFile (&FileHandle);
|
|
}
|
|
|
|
//
|
|
// If we're returning an error, make sure
|
|
// the state is sane.
|
|
if (EFI_ERROR (Status) && (Buffer != NULL)) {
|
|
FreePool (Buffer);
|
|
Buffer = NULL;
|
|
}
|
|
|
|
*SaveData = Buffer;
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Shell based UnitTestPersistenceLib library constructor.
|
|
|
|
@param[in] ImageHandle The firmware allocated handle for the EFI image.
|
|
@param[in] SystemTable A pointer to the EFI System Table.
|
|
|
|
@retval EFI_SUCCESS The constructor finished successfully.
|
|
@retval Others Error codes returned from gBS->HandleProtocol.
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
UnitTestPersistenceLibConstructor (
|
|
IN EFI_HANDLE ImageHandle,
|
|
IN EFI_SYSTEM_TABLE *SystemTable
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN Argc;
|
|
CHAR16 **Argv;
|
|
EFI_STATUS Status;
|
|
EFI_SHELL_PARAMETERS_PROTOCOL *ShellParameters;
|
|
|
|
Status = gBS->HandleProtocol (
|
|
gImageHandle,
|
|
&gEfiShellParametersProtocolGuid,
|
|
(VOID **)&ShellParameters
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
ASSERT_EFI_ERROR (Status);
|
|
goto Done;
|
|
}
|
|
|
|
Argc = ShellParameters->Argc;
|
|
Argv = ShellParameters->Argv;
|
|
|
|
Status = EFI_SUCCESS;
|
|
if ((Argc > 1) && (Argv != NULL)) {
|
|
// This might be our cue, check for whether we need to do anything
|
|
for (Index = 1; Index < Argc; Index++) {
|
|
if (StrCmp (Argv[Index], L"--CachePath") == 0) {
|
|
// Need to update the potential cache path to designated path
|
|
if (Index < Argc - 1) {
|
|
mCachePath = Argv[Index + 1];
|
|
} else {
|
|
Print (L" --CachePath <Path of where to save unit test cache files, i.e. FS0:TestFolder>\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Done:
|
|
return Status;
|
|
}
|