From 0eb522987fcd8bec9b7031ae428736936b2bc288 Mon Sep 17 00:00:00 2001 From: Michael D Kinney Date: Wed, 22 Jan 2020 10:07:17 -0800 Subject: UnitTestFrameworkPkg/Library: Add library instances https://bugzilla.tianocore.org/show_bug.cgi?id=2505 Add the following library instances that are used to build unit tests for host and target environments. * CmockaLib with cmocka submodule to: https://git.cryptomilk.org/projects/cmocka.git * DebugLibPosix - Instance of DebugLib based on POSIX APIs (e.g. printf). * MemoryAllocationLibPosix - Instance of MemoryAllocationLib based on POSIX APIs (e.g. malloc/free). * UnitTestBootLibNull - Null instance of the UnitTestBootLib * UnitTestBootLibUsbClass - UnitTestBootLib instances that supports setting boot next to a USB device. * UnitTestLib - UnitTestLib instance that is designed to work with PEI, DXE, SMM, and UEFI Shell target environments. * UnitTestLibCmocka - UintTestLib instance that uses cmocka APIs and can only be use in a host environment. * UnitTestPersistenceLibNull - Null instance of the UnitTestPersistenceLib * UnitTestPersistenceLibSimpleFileSystem - UnitTestPersistenceLib instance that can safe the unit test framework state to a media device that supports the UEFI Simple File System Protocol. * UnitTestResultReportLibConOut - UnitTestResultReportLib instance that sends report results to the UEFI standard output console. * UnitTestResultReportLibDebugLib - UnitTestResultReportLib instance that sends report results to a DebugLib using DEBUG() macros. Cc: Sean Brogan Cc: Bret Barkelew Signed-off-by: Michael D Kinney Reviewed-by: Bret Barkelew --- .../UnitTestPersistenceLibSimpleFileSystem.c | 416 +++++++++++++++++++++ .../UnitTestPersistenceLibSimpleFileSystem.inf | 47 +++ .../UnitTestPersistenceLibSimpleFileSystem.uni | 15 + 3 files changed, 478 insertions(+) create mode 100644 UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c create mode 100644 UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.inf create mode 100644 UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.uni (limited to 'UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem') diff --git a/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c b/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c new file mode 100644 index 0000000000..ccca9bfacb --- /dev/null +++ b/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.c @@ -0,0 +1,416 @@ +/** @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.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CACHE_FILE_SUFFIX L"_Cache.dat" + +/** + Generate the device 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 +EFI_DEVICE_PATH_PROTOCOL* +GetCacheFileDevicePath ( + 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; + EFI_DEVICE_PATH_PROTOCOL *CacheFileDevicePath; + + Framework = (UNIT_TEST_FRAMEWORK*)FrameworkHandle; + AppPath = NULL; + CacheFilePath = NULL; + TestName = NULL; + CacheFileDevicePath = 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", __FUNCTION__, Status)); + return NULL; + } + + // + // Before we can start, change test name from ASCII to Unicode. + // + CacheFilePathLength = AsciiStrLen (Framework->ShortTitle) + 1; + TestName = AllocatePool (CacheFilePathLength); + 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)) { + // + AppPath = ConvertDevicePathToText (LoadedImage->FilePath, TRUE, TRUE); // NOTE: This must be freed. + 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", __FUNCTION__)); + 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", __FUNCTION__)); + goto Exit; + } + + // + // 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. + + // + // Finally, try to create the device path for the thing thing. + // + CacheFileDevicePath = FileDevicePath (LoadedImage->DeviceHandle, CacheFilePath); + +Exit: + // + // Free allocated buffers. + // + if (AppPath != NULL) { + FreePool (AppPath); + } + if (CacheFilePath != NULL) { + FreePool (CacheFilePath); + } + if (TestName != NULL) { + FreePool (TestName); + } + + return CacheFileDevicePath; +} + +/** + 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 + ) +{ + EFI_DEVICE_PATH_PROTOCOL *FileDevicePath; + EFI_STATUS Status; + SHELL_FILE_HANDLE FileHandle; + + // + // NOTE: This devpath is allocated and must be freed. + // + FileDevicePath = GetCacheFileDevicePath (FrameworkHandle); + + // + // Check to see whether the file exists. If the file can be opened for + // reading, it exists. Otherwise, probably not. + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &FileHandle, + EFI_FILE_MODE_READ, + 0 + ); + if (!EFI_ERROR (Status)) { + ShellCloseFile (&FileHandle); + } + + if (FileDevicePath != NULL) { + FreePool (FileDevicePath); + } + + DEBUG ((DEBUG_VERBOSE, "%a - Returning %d\n", __FUNCTION__, !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. + + @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 UNIT_TEST_SAVE_HEADER *SaveData + ) +{ + EFI_DEVICE_PATH_PROTOCOL *FileDevicePath; + 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. + // + FileDevicePath = GetCacheFileDevicePath (FrameworkHandle); + + // + //First lets open the file if it exists so we can delete it...This is the work around for truncation + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &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", __FUNCTION__, Status)); + } + } + + // + // Now that we know the path to the file... let's open it for writing. + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &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", __FUNCTION__, Status)); + goto Exit; + } + + // + // Write the data to the file. + // + WriteCount = SaveData->SaveStateSize; + DEBUG ((DEBUG_INFO, "%a - Writing %d bytes to file...\n", __FUNCTION__, WriteCount)); + Status = ShellWriteFile ( + FileHandle, + &WriteCount, + SaveData + ); + + if (EFI_ERROR (Status) || WriteCount != SaveData->SaveStateSize) { + DEBUG ((DEBUG_ERROR, "%a - Writing to file failed! %r\n", __FUNCTION__, Status)); + } else { + DEBUG ((DEBUG_INFO, "%a - SUCCESS!\n", __FUNCTION__)); + } + + // + // No matter what, we should probably close the file. + // + ShellCloseFile (&FileHandle); + +Exit: + if (FileDevicePath != NULL) { + FreePool (FileDevicePath); + } + + 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[in] SaveData A pointer pointer that will be updated with the address + of the loaded data buffer. + + @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 UNIT_TEST_SAVE_HEADER **SaveData + ) +{ + EFI_STATUS Status; + EFI_DEVICE_PATH_PROTOCOL *FileDevicePath; + SHELL_FILE_HANDLE FileHandle; + BOOLEAN IsFileOpened; + UINT64 LargeFileSize; + UINTN FileSize; + UNIT_TEST_SAVE_HEADER *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. + // + FileDevicePath = GetCacheFileDevicePath (FrameworkHandle); + + // + // Now that we know the path to the file... let's open it for writing. + // + Status = ShellOpenFileByDevicePath ( + &FileDevicePath, + &FileHandle, + EFI_FILE_MODE_READ, + 0 + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a - Opening file for writing failed! %r\n", __FUNCTION__, 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", __FUNCTION__, 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. + Buffer = AllocatePool (FileSize); + if (Buffer == NULL) { + DEBUG ((DEBUG_ERROR, "%a - Failed to allocate a pool to hold the file contents! %r\n", __FUNCTION__, 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", __FUNCTION__, Status)); + } + +Exit: + // + // Free allocated buffers + // + if (FileDevicePath != NULL) { + FreePool (FileDevicePath); + } + 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; +} diff --git a/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.inf b/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.inf new file mode 100644 index 0000000000..c518c4e5ce --- /dev/null +++ b/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.inf @@ -0,0 +1,47 @@ +## @file +# UEFI Simple File System based version of the Unit Test Persistence Lib +# +# Instance of the Unit Test Persistence Lib that utilizes the UEFI 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.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010017 + BASE_NAME = UnitTestPersistenceLibSimpleFileSystem + MODULE_UNI_FILE = UnitTestPersistenceLibSimpleFileSystem.uni + FILE_GUID = 9200844A-CDFD-4368-B4BD-106354702605 + VERSION_STRING = 1.0 + MODULE_TYPE = UEFI_APPLICATION + LIBRARY_CLASS = UnitTestPersistenceLib + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + UnitTestPersistenceLibSimpleFileSystem.c + +[Packages] + MdePkg/MdePkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec + ShellPkg/ShellPkg.dec + +[LibraryClasses] + DebugLib + UefiBootServicesTableLib + BaseLib + ShellLib + +[Protocols] + gEfiLoadedImageProtocolGuid + gEfiSimpleFileSystemProtocolGuid + +[Guids] + gEfiFileInfoGuid + gEfiFileSystemInfoGuid diff --git a/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.uni b/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.uni new file mode 100644 index 0000000000..e6593be137 --- /dev/null +++ b/UnitTestFrameworkPkg/Library/UnitTestPersistenceLibSimpleFileSystem/UnitTestPersistenceLibSimpleFileSystem.uni @@ -0,0 +1,15 @@ +// /** @file +// UEFI Simple File System based version of the Unit Test Persistence Lib +// +// Instance of the Unit Test Persistence Lib that utilizes the UEFI 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) 2020, Intel Corporation. All rights reserved.
+// SPDX-License-Identifier: BSD-2-Clause-Patent +// +// **/ + +#string STR_MODULE_ABSTRACT #language en-US "UEFI Simple File System based version of the Unit Test Persistence Lib" + +#string STR_MODULE_DESCRIPTION #language en-US "UEFI Simple File System based version of the Unit Test Persistence Lib." -- cgit v1.2.3