Adding stack traces to exception handling in unit tests

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
Esteban Papp
2021-10-15 12:08:39 -07:00
committed by GitHub
20 changed files with 160 additions and 38 deletions
@@ -40,6 +40,12 @@ namespace AZ
static unsigned int Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount = 0, void* nativeThread = 0);
};
class StackConverter
{
public:
static unsigned int FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext);
};
class SymbolStorage
{
public:
+10 -6
View File
@@ -31,6 +31,8 @@ namespace AZ
{
namespace Debug
{
struct StackFrame;
namespace Platform
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
@@ -551,17 +553,19 @@ namespace AZ
{
StackFrame frames[25];
// Without StackFrame explicit alignment frames array is aligned to 4 bytes
// which causes the stack tracing to fail.
//size_t bla = AZStd::alignment_of<StackFrame>::value;
//printf("Alignment value %d address 0x%08x : 0x%08x\n",bla,frames);
SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
unsigned int numFrames = 0;
if (!nativeContext)
{
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrinCallstack).
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrintCallstack).
numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount);
}
unsigned int numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount, nativeContext);
else
{
numFrames = StackConverter::FromNative(frames, AZ_ARRAY_SIZE(frames), nativeContext);
}
if (numFrames)
{
SymbolStorage::DecodeFrames(frames, numFrames, lines);
@@ -17,6 +17,11 @@ namespace AZ
return false;
}
unsigned int StackConverter::FromNative(StackFrame*, unsigned int, void*)
{
return 0;
}
void SymbolStorage::LoadModuleData(const void*, unsigned int)
{}
@@ -78,6 +78,12 @@ StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned
return count;
}
unsigned int StackConverter::FromNative([[maybe_unused]] StackFrame* frames, [[maybe_unused]] unsigned int maxNumOfFrames, [[maybe_unused]] void* nativeContext)
{
AZ_Assert(false, "StackConverter::FromNative() is not supported for UnixLike platform yet");
return 0;
}
void
SymbolStorage::DecodeFrames(const StackFrame* frames, unsigned int numFrames, StackLine* textLines)
{
@@ -1048,9 +1048,9 @@ cleanup:
unsigned int
StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount, void* nativeThread)
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
unsigned int numFrames = 0;
#if defined(AZ_ENABLE_DEBUG_TOOLS)
if (nativeThread == NULL)
{
++suppressCount; // Skip current call
@@ -1079,9 +1079,8 @@ cleanup:
STACKFRAME64 sf;
memset(&sf, 0, sizeof(STACKFRAME64));
DWORD imageType;
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
imageType = IMAGE_FILE_MACHINE_AMD64;
sf.AddrPC.Offset = context.Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = context.Rsp;
@@ -1090,8 +1089,7 @@ cleanup:
sf.AddrStack.Mode = AddrModeFlat;
EnterCriticalSection(&g_csDbgHelpDll);
s32 frame = -(s32)suppressCount;
for (; frame < (s32)maxNumOfFrames; ++frame)
for (s32 frame = -static_cast<s32>(suppressCount); frame < static_cast<s32>(maxNumOfFrames); ++frame)
{
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, &context, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
{
@@ -1111,15 +1109,68 @@ cleanup:
}
LeaveCriticalSection(&g_csDbgHelpDll);
}
return numFrames;
}
#else
(void)frames;
(void)maxNumOfFrames;
(void)suppressCount;
(void)nativeThread;
return 0;
AZ_UNUSED(frames);
AZ_UNUSED(maxNumOfFrames);
AZ_UNUSED(suppressCount);
AZ_UNUSED(nativeThread);
#endif // AZ_ENABLE_DEBUG_TOOLS
return numFrames;
}
unsigned int StackConverter::FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext)
{
unsigned int numFrames = 0;
#if defined(AZ_ENABLE_DEBUG_TOOLS)
if (!g_dbgHelpLoaded)
{
LoadDbgHelp();
}
HANDLE hThread;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS);
PCONTEXT nativeContextType = reinterpret_cast<PCONTEXT>(nativeContext);
STACKFRAME64 sf;
memset(&sf, 0, sizeof(STACKFRAME64));
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
sf.AddrPC.Offset = nativeContextType->Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = nativeContextType->Rsp;
sf.AddrFrame.Mode = AddrModeFlat;
sf.AddrStack.Offset = nativeContextType->Rsp;
sf.AddrStack.Mode = AddrModeFlat;
EnterCriticalSection(&g_csDbgHelpDll);
for (unsigned int frame = 0; frame < maxNumOfFrames; ++frame)
{
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, nativeContext, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
{
break;
}
if (sf.AddrPC.Offset == sf.AddrReturn.Offset)
{
// "StackWalk64-Endless-Callstack!"
break;
}
frames[numFrames++].m_programCounter = sf.AddrPC.Offset;
}
LeaveCriticalSection(&g_csDbgHelpDll);
#else
AZ_UNUSED(frames);
AZ_UNUSED(maxNumOfFrames);
AZ_UNUSED(nativeContext);
#endif
return numFrames;
}
//////////////////////////////////////////////////////////////////////////
+4 -4
View File
@@ -1413,7 +1413,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(HashedSetDifferentAllocatorFixture, SetTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(HashedSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1435,7 +1435,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
template<typename ContainerType>
class HashedMapContainers
@@ -1811,7 +1811,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(HashedMapDifferentAllocatorFixture, MapTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(HashedMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1833,7 +1833,7 @@ namespace UnitTest
}
} , ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
namespace HashedContainerTransparentTestInternal
{
@@ -1095,7 +1095,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(TreeSetDifferentAllocatorFixture, SetTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1117,7 +1117,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeSetDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
{
@@ -1516,7 +1516,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(TreeMapDifferentAllocatorFixture, MapTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1538,7 +1538,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeMapDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
{
@@ -1595,7 +1595,7 @@ namespace UnitTest
}
};
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TEST_F(ThreadEventsDeathTest, UsingClientBus_AvoidsDeadlock)
{
EXPECT_EXIT(
@@ -1608,5 +1608,5 @@ namespace UnitTest
, ::testing::ExitedWithCode(0),".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
class UnhandledExceptions
: public ScopedAllocatorSetupFixture
{
public:
void causeAccessViolation()
{
int* someVariable = reinterpret_cast<int*>(0);
*someVariable = 0;
}
};
#if GTEST_HAS_DEATH_TEST
TEST_F(UnhandledExceptions, Handle)
{
EXPECT_DEATH(causeAccessViolation(), "");
}
#endif
}
@@ -144,14 +144,13 @@ namespace UnitTest
}
};
#if GTEST_OS_SUPPORTS_DEATH_TEST
// SPEC-2669: Disabled since it is causing hangs on Linux
#if GTEST_HAS_DEATH_TEST
TEST_F(AllocatorsTestFixtureLeakDetectionDeathTest_SKIPCODECOVERAGE, AllocatorLeak)
{
// testing that the TraceBusHook will fail on cause the test to die
EXPECT_DEATH(TestAllocatorLeak(), "");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Testing ScopedAllocatorSetupFixture. Testing that detects leaks
@@ -327,7 +327,7 @@ namespace JsonSerializationTests
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
}
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
using JsonSerializationDeathTests = JsonRegistrationContextTests;
TEST_F(JsonSerializationDeathTests, DoubleUnregisterSerializer_Asserts)
{
@@ -338,5 +338,6 @@ namespace JsonSerializationTests
}, ".*"
);
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
} //namespace JsonSerializationTests
@@ -72,6 +72,7 @@ set(FILES
Debug/AssetTracking.cpp
Debug/LocalFileEventLoggerTests.cpp
Debug/Trace.cpp
Debug/UnhandledExceptions.cpp
Name/NameJsonSerializerTests.cpp
Name/NameTests.cpp
RTTI/TypeSafeIntegralTests.cpp
+6 -2
View File
@@ -92,10 +92,14 @@ namespace AZ
void ApplyGlobalParameters(int* argc, char** argv)
{
// this is a hook that can be used to apply any other global non-google parameters
// that we use.
// this is a hook that can be used to apply any other global parameters that we use.
AZ_UNUSED(argc);
AZ_UNUSED(argv);
// Disable gtest catching unhandled exceptions, instead, AzTestRunner will do it through:
// AZ::Debug::Trace::HandleExceptions(true). This gives us a stack trace when the exception
// is thrown (googletest does not).
testing::FLAGS_gtest_catch_exceptions = false;
}
//! Print out parameters that are not used by the framework
@@ -291,6 +291,9 @@ namespace AssetBundler
int main(int argc, char* argv[])
{
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
INVOKE_AZ_UNIT_TEST_MAIN();
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
@@ -262,7 +262,7 @@ namespace UnitTests
auto result = m_data->m_reporter->ComputeDestination(entryContainer, m_data->m_platformConfig.GetScanFolderByPath(scanFolderEntry.m_scanFolder.c_str()), source, destination, destInfo);
ASSERT_EQ(result.IsSuccess(), expectSuccess) << result.GetError().c_str();
ASSERT_EQ(result.IsSuccess(), expectSuccess) << (!result.IsSuccess() ? result.GetError().c_str() : "");
if (expectSuccess)
{
@@ -29,6 +29,9 @@ int main(int argc, char* argv[])
{
qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "1");
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
// If "--unittest" is present on the command line, run unit testing
// and return immediately. Otherwise, continue as normal.
AZ::Test::addTestEnvironment(new BaseAssetProcessorTestEnvironment());
+2
View File
@@ -189,6 +189,8 @@ namespace AzTestRunner
int wrapped_main(int argc/*=0*/, char** argv/*=nullptr*/)
{
AZ::Debug::Trace::HandleExceptions(true);
if (argc>0 && argv!=nullptr)
{
return wrapped_command_arg_main(argc, argv);
+3
View File
@@ -18,6 +18,9 @@ int runDefaultRunner(int argc, char* argv[])
int main(int argc, char* argv[])
{
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
if (argc == 1)
{
// if no parameters are provided, add the --unittests parameter
@@ -23,6 +23,9 @@ int runDefaultRunner(int argc, char* argv[])
int main(int argc, char* argv[])
{
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
// ran with no parameters?
if (argc == 1)
{
@@ -48,10 +48,10 @@ namespace WhiteBox
float time;
AZ::Vector3 normal;
const float rayLength = 1000.0f;
const int intersected = AZ::Intersect::IntersectSegmentTriangleCCW(
const bool intersected = AZ::Intersect::IntersectSegmentTriangleCCW(
rayOrigin, rayOrigin + rayDirection * rayLength, p0, p1, p2, normal, time);
if (intersected != 0)
if (intersected)
{
rayIntersectionDistance = time * rayLength;
intersectedTriangleIndex = triangleIndex / 3;