diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c | 856 |
1 files changed, 856 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c b/src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c new file mode 100644 index 00000000..a240de55 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/UnitTestFrameworkPkg/Library/UnitTestLib/UnitTestLib.c @@ -0,0 +1,856 @@ +/** + Implement UnitTestLib + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include <Uefi.h> +#include <Library/UnitTestLib.h> +#include <Library/BaseLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/MemoryAllocationLib.h> +#include <Library/DebugLib.h> +#include <Library/UnitTestPersistenceLib.h> +#include <Library/UnitTestResultReportLib.h> + +/// +/// Forward declaration of prototype +/// +STATIC +VOID +UpdateTestFromSave ( + IN OUT UNIT_TEST *Test, + IN UNIT_TEST_SAVE_HEADER *SavedState + ); + +/** + This function will determine whether the short name violates any rules that would + prevent it from being used as a reporting name or as a serialization name. + + Example: If the name cannot be serialized to a filesystem file name. + + @param[in] ShortTitleString A pointer to the short title string to be evaluated. + + @retval TRUE The string is acceptable. + @retval FALSE The string should not be used. + +**/ +STATIC +BOOLEAN +IsFrameworkShortNameValid ( + IN CHAR8 *ShortTitleString + ) +{ + // TODO: Finish this function. + return TRUE; +} + +STATIC +CHAR8* +AllocateAndCopyString ( + IN CHAR8 *StringToCopy + ) +{ + CHAR8 *NewString; + UINTN NewStringLength; + + NewString = NULL; + NewStringLength = AsciiStrnLenS (StringToCopy, UNIT_TEST_MAX_STRING_LENGTH) + 1; + NewString = AllocatePool (NewStringLength * sizeof( CHAR8 )); + if (NewString != NULL) { + AsciiStrCpyS (NewString, NewStringLength, StringToCopy); + } + return NewString; +} + +STATIC +VOID +SetFrameworkFingerprint ( + OUT UINT8 *Fingerprint, + IN UNIT_TEST_FRAMEWORK *Framework + ) +{ + UINT32 NewFingerprint; + + // For this one we'll just use the title and version as the unique fingerprint. + NewFingerprint = CalculateCrc32( Framework->Title, (AsciiStrLen( Framework->Title ) * sizeof( CHAR8 )) ); + NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32( Framework->VersionString, (AsciiStrLen( Framework->VersionString ) * sizeof( CHAR8 )) ); + + CopyMem( Fingerprint, &NewFingerprint, UNIT_TEST_FINGERPRINT_SIZE ); + return; +} + +STATIC +VOID +SetSuiteFingerprint ( + OUT UINT8 *Fingerprint, + IN UNIT_TEST_FRAMEWORK *Framework, + IN UNIT_TEST_SUITE *Suite + ) +{ + UINT32 NewFingerprint; + + // For this one, we'll use the fingerprint from the framework, and the title of the suite. + NewFingerprint = CalculateCrc32( &Framework->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE ); + NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32( Suite->Title, (AsciiStrLen( Suite->Title ) * sizeof( CHAR8 )) ); + NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32( Suite->Name, (AsciiStrLen(Suite->Name) * sizeof(CHAR8)) ); + + CopyMem( Fingerprint, &NewFingerprint, UNIT_TEST_FINGERPRINT_SIZE ); + return; +} + +STATIC +VOID +SetTestFingerprint ( + OUT UINT8 *Fingerprint, + IN UNIT_TEST_SUITE *Suite, + IN UNIT_TEST *Test + ) +{ + UINT32 NewFingerprint; + + // For this one, we'll use the fingerprint from the suite, and the description and classname of the test. + NewFingerprint = CalculateCrc32( &Suite->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE ); + NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32( Test->Description, (AsciiStrLen( Test->Description ) * sizeof( CHAR8 )) ); + NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32( Test->Name, (AsciiStrLen(Test->Name) * sizeof(CHAR8)) ); + + CopyMem( Fingerprint, &NewFingerprint, UNIT_TEST_FINGERPRINT_SIZE ); + return; +} + +STATIC +BOOLEAN +CompareFingerprints ( + IN UINT8 *FingerprintA, + IN UINT8 *FingerprintB + ) +{ + return (CompareMem( FingerprintA, FingerprintB, UNIT_TEST_FINGERPRINT_SIZE ) == 0); +} + +/** + Cleanup a test framework. + + After tests are run, this will teardown the entire framework and free all + allocated data within. + + @param[in] FrameworkHandle A handle to the current running framework that + dispatched the test. Necessary for recording + certain test events with the framework. + + @retval EFI_SUCCESS All resources associated with framework were + freed. + @retval EFI_INVALID_PARAMETER FrameworkHandle is NULL. +**/ +EFI_STATUS +EFIAPI +FreeUnitTestFramework ( + IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle + ) +{ + // TODO: Finish this function. + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +FreeUnitTestSuiteEntry ( + IN UNIT_TEST_SUITE_LIST_ENTRY *SuiteEntry + ) +{ + // TODO: Finish this function. + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +FreeUnitTestTestEntry ( + IN UNIT_TEST_LIST_ENTRY *TestEntry + ) +{ + // TODO: Finish this function. + return EFI_SUCCESS; +} + +/** + Method to Initialize the Unit Test framework. This function registers the + test name and also initializes the internal state of the test framework to + receive any new suites and tests. + + @param[out] FrameworkHandle Unit test framework to be created. + @param[in] Title Null-terminated ASCII string that is the user + friendly name of the framework. String is + copied. + @param[in] ShortTitle Null-terminated ASCII short string that is the + short name of the framework with no spaces. + String is copied. + @param[in] VersionString Null-terminated ASCII version string for the + framework. String is copied. + + @retval EFI_SUCCESS The unit test framework was initialized. + @retval EFI_INVALID_PARAMETER FrameworkHandle is NULL. + @retval EFI_INVALID_PARAMETER Title is NULL. + @retval EFI_INVALID_PARAMETER ShortTitle is NULL. + @retval EFI_INVALID_PARAMETER VersionString is NULL. + @retval EFI_INVALID_PARAMETER ShortTitle is invalid. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + initialize the unit test framework. +**/ +EFI_STATUS +EFIAPI +InitUnitTestFramework ( + OUT UNIT_TEST_FRAMEWORK_HANDLE *FrameworkHandle, + IN CHAR8 *Title, + IN CHAR8 *ShortTitle, + IN CHAR8 *VersionString + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE NewFrameworkHandle; + UNIT_TEST_FRAMEWORK *NewFramework; + + Status = EFI_SUCCESS; + NewFramework = NULL; + + // + // First, check all pointers and make sure nothing's broked. + // + if (FrameworkHandle == NULL || Title == NULL || + ShortTitle == NULL || VersionString == NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Next, determine whether all of the strings are good to use. + // + if (!IsFrameworkShortNameValid (ShortTitle)) { + return EFI_INVALID_PARAMETER; + } + + // + // Next, set aside some space to start messing with the framework. + // + NewFramework = AllocateZeroPool (sizeof (UNIT_TEST_FRAMEWORK)); + if (NewFramework == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Next, set up all the test data. + // + NewFrameworkHandle = (UNIT_TEST_FRAMEWORK_HANDLE)NewFramework; + NewFramework->Title = AllocateAndCopyString (Title); + NewFramework->ShortTitle = AllocateAndCopyString (ShortTitle); + NewFramework->VersionString = AllocateAndCopyString (VersionString); + NewFramework->Log = NULL; + NewFramework->CurrentTest = NULL; + NewFramework->SavedState = NULL; + if (NewFramework->Title == NULL || + NewFramework->ShortTitle == NULL || + NewFramework->VersionString == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + InitializeListHead (&(NewFramework->TestSuiteList)); + + // + // Create the framework fingerprint. + // + SetFrameworkFingerprint (&NewFramework->Fingerprint[0], NewFramework); + + // + // If there is a persisted context, load it now. + // + if (DoesCacheExist (NewFrameworkHandle)) { + Status = LoadUnitTestCache (NewFrameworkHandle, (UNIT_TEST_SAVE_HEADER**)(&NewFramework->SavedState)); + if (EFI_ERROR (Status)) { + // + // Don't actually report it as an error, but emit a warning. + // + DEBUG (( DEBUG_ERROR, "%a - Cache was detected, but failed to load.\n", __FUNCTION__ )); + Status = EFI_SUCCESS; + } + } + +Exit: + // + // If we're good, then let's copy the framework. + // + if (!EFI_ERROR (Status)) { + *FrameworkHandle = NewFrameworkHandle; + } else { + // + // Otherwise, we need to undo this horrible thing that we've done. + // + FreeUnitTestFramework (NewFrameworkHandle); + } + + return Status; +} + +/** + Registers a Unit Test Suite in the Unit Test Framework. + At least one test suite must be registered, because all test cases must be + within a unit test suite. + + @param[out] SuiteHandle Unit test suite to create + @param[in] FrameworkHandle Unit test framework to add unit test suite to + @param[in] Title Null-terminated ASCII string that is the user + friendly name of the test suite. String is + copied. + @param[in] Name Null-terminated ASCII string that is the short + name of the test suite with no spaces. String + is copied. + @param[in] Setup Setup function, runs before suite. This is an + optional parameter that may be NULL. + @param[in] Teardown Teardown function, runs after suite. This is an + optional parameter that may be NULL. + + @retval EFI_SUCCESS The unit test suite was created. + @retval EFI_INVALID_PARAMETER SuiteHandle is NULL. + @retval EFI_INVALID_PARAMETER FrameworkHandle is NULL. + @retval EFI_INVALID_PARAMETER Title is NULL. + @retval EFI_INVALID_PARAMETER Name is NULL. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + initialize the unit test suite. +**/ +EFI_STATUS +EFIAPI +CreateUnitTestSuite ( + OUT UNIT_TEST_SUITE_HANDLE *SuiteHandle, + IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle, + IN CHAR8 *Title, + IN CHAR8 *Name, + IN UNIT_TEST_SUITE_SETUP Setup OPTIONAL, + IN UNIT_TEST_SUITE_TEARDOWN Teardown OPTIONAL + ) +{ + EFI_STATUS Status; + UNIT_TEST_SUITE_LIST_ENTRY *NewSuiteEntry; + UNIT_TEST_FRAMEWORK *Framework; + + Status = EFI_SUCCESS; + Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle; + + // + // First, let's check to make sure that our parameters look good. + // + if ((SuiteHandle == NULL) || (Framework == NULL) || (Title == NULL) || (Name == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // + // Create the new entry. + // + NewSuiteEntry = AllocateZeroPool (sizeof (UNIT_TEST_SUITE_LIST_ENTRY)); + if (NewSuiteEntry == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Copy the fields we think we need. + // + NewSuiteEntry->UTS.NumTests = 0; + NewSuiteEntry->UTS.Title = AllocateAndCopyString (Title); + NewSuiteEntry->UTS.Name = AllocateAndCopyString (Name); + NewSuiteEntry->UTS.Setup = Setup; + NewSuiteEntry->UTS.Teardown = Teardown; + NewSuiteEntry->UTS.ParentFramework = FrameworkHandle; + InitializeListHead (&(NewSuiteEntry->Entry)); // List entry for sibling suites. + InitializeListHead (&(NewSuiteEntry->UTS.TestCaseList)); // List entry for child tests. + if (NewSuiteEntry->UTS.Title == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + + if (NewSuiteEntry->UTS.Name == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + + // + // Create the suite fingerprint. + // + SetSuiteFingerprint( &NewSuiteEntry->UTS.Fingerprint[0], Framework, &NewSuiteEntry->UTS ); + +Exit: + // + // If everything is going well, add the new suite to the tail list for the framework. + // + if (!EFI_ERROR( Status )) { + InsertTailList (&(Framework->TestSuiteList), (LIST_ENTRY *)NewSuiteEntry); + *SuiteHandle = (UNIT_TEST_SUITE_HANDLE)(&NewSuiteEntry->UTS); + } else { + // + // Otherwise, make with the destruction. + // + FreeUnitTestSuiteEntry (NewSuiteEntry); + } + + return Status; +} + +/** + Adds test case to Suite + + @param[in] SuiteHandle Unit test suite to add test to. + @param[in] Description Null-terminated ASCII string that is the user + friendly description of a test. String is copied. + @param[in] Name Null-terminated ASCII string that is the short name + of the test with no spaces. String is copied. + @param[in] Function Unit test function. + @param[in] Prerequisite Prerequisite function, runs before test. This is + an optional parameter that may be NULL. + @param[in] CleanUp Clean up function, runs after test. This is an + optional parameter that may be NULL. + @param[in] Context Pointer to context. This is an optional parameter + that may be NULL. + + @retval EFI_SUCCESS The unit test case was added to Suite. + @retval EFI_INVALID_PARAMETER SuiteHandle is NULL. + @retval EFI_INVALID_PARAMETER Description is NULL. + @retval EFI_INVALID_PARAMETER Name is NULL. + @retval EFI_INVALID_PARAMETER Function is NULL. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + add the unit test case to Suite. +**/ +EFI_STATUS +EFIAPI +AddTestCase ( + IN UNIT_TEST_SUITE_HANDLE SuiteHandle, + IN CHAR8 *Description, + IN CHAR8 *Name, + IN UNIT_TEST_FUNCTION Function, + IN UNIT_TEST_PREREQUISITE Prerequisite OPTIONAL, + IN UNIT_TEST_CLEANUP CleanUp OPTIONAL, + IN UNIT_TEST_CONTEXT Context OPTIONAL + ) +{ + EFI_STATUS Status; + UNIT_TEST_LIST_ENTRY *NewTestEntry; + UNIT_TEST_FRAMEWORK *ParentFramework; + UNIT_TEST_SUITE *Suite; + + Status = EFI_SUCCESS; + Suite = (UNIT_TEST_SUITE *)SuiteHandle; + + // + // First, let's check to make sure that our parameters look good. + // + if ((Suite == NULL) || (Description == NULL) || (Name == NULL) || (Function == NULL)) { + return EFI_INVALID_PARAMETER; + } + + ParentFramework = (UNIT_TEST_FRAMEWORK *)Suite->ParentFramework; + // + // Create the new entry. + NewTestEntry = AllocateZeroPool (sizeof( UNIT_TEST_LIST_ENTRY )); + if (NewTestEntry == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Copy the fields we think we need. + NewTestEntry->UT.Description = AllocateAndCopyString (Description); + NewTestEntry->UT.Name = AllocateAndCopyString (Name); + NewTestEntry->UT.FailureType = FAILURETYPE_NOFAILURE; + NewTestEntry->UT.FailureMessage[0] = '\0'; + NewTestEntry->UT.Log = NULL; + NewTestEntry->UT.Prerequisite = Prerequisite; + NewTestEntry->UT.CleanUp = CleanUp; + NewTestEntry->UT.RunTest = Function; + NewTestEntry->UT.Context = Context; + NewTestEntry->UT.Result = UNIT_TEST_PENDING; + NewTestEntry->UT.ParentSuite = SuiteHandle; + InitializeListHead (&(NewTestEntry->Entry)); // List entry for sibling tests. + if (NewTestEntry->UT.Description == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + if (NewTestEntry->UT.Name == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Exit; + } + + // + // Create the test fingerprint. + // + SetTestFingerprint (&NewTestEntry->UT.Fingerprint[0], Suite, &NewTestEntry->UT); + + // TODO: Make sure that duplicate fingerprints cannot be created. + + // + // If there is saved test data, update this record. + // + if (ParentFramework->SavedState != NULL) { + UpdateTestFromSave (&NewTestEntry->UT, ParentFramework->SavedState); + } + +Exit: + // + // If everything is going well, add the new suite to the tail list for the framework. + // + if (!EFI_ERROR (Status)) { + InsertTailList (&(Suite->TestCaseList), (LIST_ENTRY*)NewTestEntry); + Suite->NumTests++; + } else { + // + // Otherwise, make with the destruction. + // + FreeUnitTestTestEntry (NewTestEntry); + } + + return Status; +} + +STATIC +VOID +UpdateTestFromSave ( + IN OUT UNIT_TEST *Test, + IN UNIT_TEST_SAVE_HEADER *SavedState + ) +{ + UNIT_TEST_SAVE_TEST *CurrentTest; + UNIT_TEST_SAVE_TEST *MatchingTest; + UINT8 *FloatingPointer; + UNIT_TEST_SAVE_CONTEXT *SavedContext; + UINTN Index; + + // + // First, evaluate the inputs. + // + if (Test == NULL || SavedState == NULL) { + return; + } + if (SavedState->TestCount == 0) { + return; + } + + // + // Next, determine whether a matching test can be found. + // Start at the beginning. + // + MatchingTest = NULL; + FloatingPointer = (UINT8 *)SavedState + sizeof (*SavedState); + for (Index = 0; Index < SavedState->TestCount; Index++) { + CurrentTest = (UNIT_TEST_SAVE_TEST *)FloatingPointer; + if (CompareFingerprints (&Test->Fingerprint[0], &CurrentTest->Fingerprint[0])) { + MatchingTest = CurrentTest; + // + // If there's a saved context, it's important that we iterate through the entire list. + // + if (!SavedState->HasSavedContext) { + break; + } + } + + // + // If we didn't find it, we have to increment to the next test. + // + FloatingPointer = (UINT8 *)CurrentTest + CurrentTest->Size; + } + + // + // If a matching test was found, copy the status. + // + if (MatchingTest) { + // + // Override the test status with the saved status. + // + Test->Result = MatchingTest->Result; + + Test->FailureType = MatchingTest->FailureType; + AsciiStrnCpyS ( + &Test->FailureMessage[0], + UNIT_TEST_TESTFAILUREMSG_LENGTH, + &MatchingTest->FailureMessage[0], + UNIT_TEST_TESTFAILUREMSG_LENGTH + ); + + // + // If there is a log string associated, grab that. + // We can tell that there's a log string because the "size" will be larger than + // the structure size. + // IMPORTANT NOTE: There are security implications here. + // This data is user-supplied and we're about to play kinda + // fast and loose with data buffers. + // + if (MatchingTest->Size > sizeof (UNIT_TEST_SAVE_TEST)) { + UnitTestLogInit (Test, (UINT8 *)MatchingTest->Log, MatchingTest->Size - sizeof (UNIT_TEST_SAVE_TEST)); + } + } + + // + // If the saved context exists and matches this test, grab it, too. + // + if (SavedState->HasSavedContext) { + // + // If there was a saved context, the "matching test" loop will have placed the FloatingPointer + // at the beginning of the context structure. + // + SavedContext = (UNIT_TEST_SAVE_CONTEXT *)FloatingPointer; + if ((SavedContext->Size - sizeof (UNIT_TEST_SAVE_CONTEXT)) > 0 && + CompareFingerprints (&Test->Fingerprint[0], &SavedContext->Fingerprint[0])) { + // + // Override the test context with the saved context. + // + Test->Context = (VOID*)SavedContext->Data; + } + } +} + +STATIC +UNIT_TEST_SAVE_HEADER* +SerializeState ( + IN UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle, + IN UNIT_TEST_CONTEXT ContextToSave, OPTIONAL + IN UINTN ContextToSaveSize + ) +{ + UNIT_TEST_FRAMEWORK *Framework; + UNIT_TEST_SAVE_HEADER *Header; + LIST_ENTRY *SuiteListHead; + LIST_ENTRY *Suite; + LIST_ENTRY *TestListHead; + LIST_ENTRY *Test; + UINT32 TestCount; + UINT32 TotalSize; + UINTN LogSize; + UNIT_TEST_SAVE_TEST *TestSaveData; + UNIT_TEST_SAVE_CONTEXT *TestSaveContext; + UNIT_TEST *UnitTest; + UINT8 *FloatingPointer; + + Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle; + Header = NULL; + + // + // First, let's not make assumptions about the parameters. + // + if (Framework == NULL || + (ContextToSave != NULL && ContextToSaveSize == 0) || + ContextToSaveSize > MAX_UINT32) { + return NULL; + } + + // + // Next, we've gotta figure out the resources that will be required to serialize the + // the framework state so that we can persist it. + // To start with, we're gonna need a header. + // + TotalSize = sizeof (UNIT_TEST_SAVE_HEADER); + // + // Now we need to figure out how many tests there are. + // + TestCount = 0; + // + // Iterate all suites. + // + SuiteListHead = &Framework->TestSuiteList; + for (Suite = GetFirstNode (SuiteListHead); Suite != SuiteListHead; Suite = GetNextNode (SuiteListHead, Suite)) { + // + // Iterate all tests within the suite. + // + TestListHead = &((UNIT_TEST_SUITE_LIST_ENTRY *)Suite)->UTS.TestCaseList; + for (Test = GetFirstNode (TestListHead); Test != TestListHead; Test = GetNextNode (TestListHead, Test)) { + UnitTest = &((UNIT_TEST_LIST_ENTRY *)Test)->UT; + // + // Account for the size of a test structure. + // + TotalSize += sizeof( UNIT_TEST_SAVE_TEST ); + // + // If there's a log, make sure to account for the log size. + // + if (UnitTest->Log != NULL) { + // + // The +1 is for the NULL character. Can't forget the NULL character. + // + LogSize = (AsciiStrLen (UnitTest->Log) + 1) * sizeof (CHAR8); + ASSERT (LogSize < MAX_UINT32); + TotalSize += (UINT32)LogSize; + } + // + // Increment the test count. + // + TestCount++; + } + } + // + // If there are no tests, we're done here. + // + if (TestCount == 0) { + return NULL; + } + // + // Add room for the context, if there is one. + // + if (ContextToSave != NULL) { + TotalSize += sizeof (UNIT_TEST_SAVE_CONTEXT) + (UINT32)ContextToSaveSize; + } + + // + // Now that we know the size, we need to allocate space for the serialized output. + // + Header = AllocateZeroPool (TotalSize); + if (Header == NULL) { + return NULL; + } + + // + // Alright, let's start setting up some data. + // + Header->Version = UNIT_TEST_PERSISTENCE_LIB_VERSION; + Header->SaveStateSize = TotalSize; + CopyMem (&Header->Fingerprint[0], &Framework->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE); + CopyMem (&Header->StartTime, &Framework->StartTime, sizeof (EFI_TIME)); + Header->TestCount = TestCount; + Header->HasSavedContext = FALSE; + + // + // Start adding all of the test cases. + // Set the floating pointer to the start of the current test save buffer. + // + FloatingPointer = (UINT8*)Header + sizeof( UNIT_TEST_SAVE_HEADER ); + // + // Iterate all suites. + // + SuiteListHead = &Framework->TestSuiteList; + for (Suite = GetFirstNode (SuiteListHead); Suite != SuiteListHead; Suite = GetNextNode (SuiteListHead, Suite)) { + // + // Iterate all tests within the suite. + // + TestListHead = &((UNIT_TEST_SUITE_LIST_ENTRY *)Suite)->UTS.TestCaseList; + for (Test = GetFirstNode (TestListHead); Test != TestListHead; Test = GetNextNode (TestListHead, Test)) { + TestSaveData = (UNIT_TEST_SAVE_TEST *)FloatingPointer; + UnitTest = &((UNIT_TEST_LIST_ENTRY *)Test)->UT; + + // + // Save the fingerprint. + // + CopyMem (&TestSaveData->Fingerprint[0], &UnitTest->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE); + + // + // Save the result. + // + TestSaveData->Result = UnitTest->Result; + TestSaveData->FailureType = UnitTest->FailureType; + AsciiStrnCpyS (&TestSaveData->FailureMessage[0], UNIT_TEST_TESTFAILUREMSG_LENGTH, &UnitTest->FailureMessage[0], UNIT_TEST_TESTFAILUREMSG_LENGTH); + + + // + // If there is a log, save the log. + // + FloatingPointer += sizeof (UNIT_TEST_SAVE_TEST); + if (UnitTest->Log != NULL) { + // + // The +1 is for the NULL character. Can't forget the NULL character. + // + LogSize = (AsciiStrLen (UnitTest->Log) + 1) * sizeof (CHAR8); + CopyMem (FloatingPointer, UnitTest->Log, LogSize); + FloatingPointer += LogSize; + } + + // + // Update the size once the structure is complete. + // NOTE: Should this be a straight cast without validation? + // + TestSaveData->Size = (UINT32)(FloatingPointer - (UINT8 *)TestSaveData); + } + } + + // + // If there is a context to save, let's do that now. + // + if (ContextToSave != NULL && Framework->CurrentTest != NULL) { + TestSaveContext = (UNIT_TEST_SAVE_CONTEXT*)FloatingPointer; + TestSaveContext->Size = (UINT32)ContextToSaveSize + sizeof (UNIT_TEST_SAVE_CONTEXT); + CopyMem (&TestSaveContext->Fingerprint[0], &Framework->CurrentTest->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE); + CopyMem (((UINT8 *)TestSaveContext + sizeof (UNIT_TEST_SAVE_CONTEXT)), ContextToSave, ContextToSaveSize); + Header->HasSavedContext = TRUE; + } + + return Header; +} + +/** + Leverages a framework-specific mechanism (see UnitTestPersistenceLib if you're + a framework author) to save the state of the executing framework along with + any allocated data so that the test may be resumed upon reentry. A test case + should pass any needed context (which, to prevent an infinite loop, should be + at least the current execution count) which will be saved by the framework and + passed to the test case upon resume. + + This should be called while the current test framework is valid and active. It is + generally called from within a test case prior to quitting or rebooting. + + @param[in] ContextToSave A buffer of test case-specific data to be saved + along with framework state. Will be passed as + "Context" to the test case upon resume. This + is an optional parameter that may be NULL. + @param[in] ContextToSaveSize Size of the ContextToSave buffer. + + @retval EFI_SUCCESS The framework state and context were saved. + @retval EFI_NOT_FOUND An active framework handle was not found. + @retval EFI_INVALID_PARAMETER ContextToSave is not NULL and + ContextToSaveSize is 0. + @retval EFI_INVALID_PARAMETER ContextToSave is >= 4GB. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to + save the framework and context state. + @retval EFI_DEVICE_ERROR The framework and context state could not be + saved to a persistent storage device due to a + device error. +**/ +EFI_STATUS +EFIAPI +SaveFrameworkState ( + IN UNIT_TEST_CONTEXT ContextToSave OPTIONAL, + IN UINTN ContextToSaveSize + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE FrameworkHandle; + UNIT_TEST_SAVE_HEADER *Header; + + Header = NULL; + FrameworkHandle = GetActiveFrameworkHandle (); + + // + // Return a unique error code if the framework is not set. + // + if (FrameworkHandle == NULL) { + return EFI_NOT_FOUND; + } + + // + // First, let's not make assumptions about the parameters. + // + if ((ContextToSave != NULL && ContextToSaveSize == 0) || + ContextToSaveSize > MAX_UINT32) { + return EFI_INVALID_PARAMETER; + } + + // + // Now, let's package up all the data for saving. + // + Header = SerializeState (FrameworkHandle, ContextToSave, ContextToSaveSize); + if (Header == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // All that should be left to do is save it using the associated persistence lib. + // + Status = SaveUnitTestCache (FrameworkHandle, Header); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a - Could not save state! %r\n", __FUNCTION__, Status)); + Status = EFI_DEVICE_ERROR; + } + + // + // Free data that was used. + // + FreePool (Header); + + return Status; +} |