diff --git a/flang/unittests/RuntimeGTest/CMakeLists.txt b/flang/unittests/RuntimeGTest/CMakeLists.txt --- a/flang/unittests/RuntimeGTest/CMakeLists.txt +++ b/flang/unittests/RuntimeGTest/CMakeLists.txt @@ -1,5 +1,7 @@ add_flang_unittest(FlangRuntimeTests CharacterTest.cpp + RuntimeCrashTest.cpp + CrashHandlerFixture.cpp ) target_link_libraries(FlangRuntimeTests diff --git a/flang/unittests/RuntimeGTest/CrashHandlerFixture.h b/flang/unittests/RuntimeGTest/CrashHandlerFixture.h new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/CrashHandlerFixture.h @@ -0,0 +1,21 @@ +//===-- flang/unittests/RuntimeGTest/CrashHandlerFixture.h ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// Test fixture registers a custom crash handler to ensure death tests fail +/// with expected message. +// +//===----------------------------------------------------------------------===// +#ifndef FORTRAN_CRASH_HANDLER_FIXTURE_H_ +#define FORTRAN_CRASH_HANDLER_FIXTURE_H_ +#include + +struct CrashHandlerFixture : testing::Test { + void SetUp(); +}; + +#endif diff --git a/flang/unittests/RuntimeGTest/CrashHandlerFixture.cpp b/flang/unittests/RuntimeGTest/CrashHandlerFixture.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/CrashHandlerFixture.cpp @@ -0,0 +1,34 @@ +//===-- flang/unittests/RuntimeGTest/CrashHandlerFixture.cpp ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "CrashHandlerFixture.h" +#include "../../runtime/terminator.h" +#include +#include + +// Replaces Fortran runtime's crash handler so we can verify the crash message +[[noreturn]] static void CatchCrash( + const char *sourceFile, int sourceLine, const char *message, va_list &ap) { + char buffer[1000]; + std::vsnprintf(buffer, sizeof buffer, message, ap); + va_end(ap); + llvm::errs() + << "Test " + << ::testing::UnitTest::GetInstance()->current_test_info()->name() + << " crashed in file " + << (sourceFile ? sourceFile : "unknown source file") << '(' << sourceLine + << "): " << buffer << '\n'; + std::exit(EXIT_FAILURE); +} + +// Register the crash handler above when creating each unit test in this suite +void CrashHandlerFixture::SetUp() { + static bool isCrashHanlderRegistered{false}; + if (not isCrashHanlderRegistered) + Fortran::runtime::Terminator::RegisterCrashHandler(CatchCrash); + isCrashHanlderRegistered = true; +} diff --git a/flang/unittests/RuntimeGTest/RuntimeCrashTest.cpp b/flang/unittests/RuntimeGTest/RuntimeCrashTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/RuntimeCrashTest.cpp @@ -0,0 +1,157 @@ +//===-- flang/unittests/RuntimeGTest/CrashHandlerFixture.cpp ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// Selected APIs are tested here to support development of unit tests for other +/// runtime components and ensure the test fixture handles crashes as we expect. +// +//===----------------------------------------------------------------------===// +#include "CrashHandlerFixture.h" +#include "../../runtime/io-api.h" +#include "../../runtime/terminator.h" +#include + +using namespace Fortran::runtime; +using namespace Fortran::runtime::io; + +//------------------------------------------------------------------------------ +/// Test crashes through direct calls to terminator methods +//------------------------------------------------------------------------------ +struct TestTerminator : CrashHandlerFixture {}; + +#define TEST_CRASH_HANDLER_MESSAGE \ + "Intentionally crashing runtime for unit test" + +TEST(TestTerminator, CrashTest) { + static Fortran::runtime::Terminator t; + ASSERT_DEATH(t.Crash(TEST_CRASH_HANDLER_MESSAGE), TEST_CRASH_HANDLER_MESSAGE); +} + +#undef TEST_CRASH_HANDLER_MESSAGE + +TEST(TestTerminator, CheckFailedLocationTest) { + static Fortran::runtime::Terminator t; + ASSERT_DEATH(t.CheckFailed("predicate", "someFileName", 789), + "RUNTIME_CHECK\\(predicate\\) failed at someFileName\\(789\\)"); +} + +TEST(TestTerminator, CheckFailedTest) { + static Fortran::runtime::Terminator t; + ASSERT_DEATH(t.CheckFailed("predicate"), + "RUNTIME_CHECK\\(predicate\\) failed at \\(null\\)\\(0\\)"); +} + +//------------------------------------------------------------------------------ +/// Test misuse of io api +//------------------------------------------------------------------------------ +struct TestIOCrash : CrashHandlerFixture {}; + +TEST(TestIOCrash, FormatDescriptorWriteMismatchTest) { + static constexpr int bufferSize{4}; + static char buffer[bufferSize]; + static const char *format{"(A4)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + ASSERT_DEATH(IONAME(OutputInteger64)(cookie, /*output_value=*/0xfeedface), + "Data edit descriptor 'A' may not be used with an INTEGER data item"); +} + +TEST(TestIOCrash, InvalidFormatCharacterTest) { + static constexpr int bufferSize{1}; + static char buffer[bufferSize]; + static const char *format{"(C1)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + ASSERT_DEATH(IONAME(OutputInteger64)(cookie, /*output_value=*/0xfeedface), + "Unknown 'C' edit descriptor in FORMAT"); +} + +//------------------------------------------------------------------------------ +/// Test buffer overwrites with Output* functions +/// Each test performs the tested IO operation correctly first, before causing +/// an overwrite to demonstrate that the failure is caused by the overwrite and +/// not a misuse of the API. +//------------------------------------------------------------------------------ +TEST(TestIOCrash, OverwriteBufferAsciiTest) { + static constexpr int bufferSize{4}; + static char buffer[bufferSize]; + static const char *format{"(A4)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + IONAME(OutputAscii)(cookie, "four", bufferSize); + ASSERT_DEATH(IONAME(OutputAscii)(cookie, "Too many characters!", 20), + "Internal write overran available records"); +} + +TEST(TestIOCrash, OverwriteBufferCharacterTest) { + static constexpr int bufferSize{1}; + static char buffer[bufferSize]; + static const char *format{"(A1)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + IONAME(OutputCharacter)(cookie, "a", 1); + ASSERT_DEATH(IONAME(OutputCharacter)(cookie, "a", 1), + "Internal write overran available records"); +} + +TEST(TestIOCrash, OverwriteBufferLogicalTest) { + static constexpr int bufferSize{1}; + static char buffer[bufferSize]; + static const char *format{"(L1)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + IONAME(OutputLogical)(cookie, true); + ASSERT_DEATH(IONAME(OutputLogical)(cookie, true), + "Internal write overran available records"); +} + +TEST(TestIOCrash, OverwriteBufferRealTest) { + static constexpr int bufferSize{1}; + static char buffer[bufferSize]; + static const char *format{"(F1)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + IONAME(OutputReal32)(cookie, 1.); + EXPECT_DEATH(IONAME(OutputReal32)(cookie, 1.), + "Internal write overran available records"); + + std::memset(buffer, '\0', bufferSize); + cookie = IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format)); + IONAME(OutputReal64)(cookie, 1.); + EXPECT_DEATH(IONAME(OutputReal64)(cookie, 1.), + "Internal write overran available records"); +} + +TEST(TestIOCrash, OverwriteBufferComplexTest) { + static constexpr int bufferSize{8}; + static char buffer[bufferSize]; + static const char *format{"(Z1,Z1)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + IONAME(OutputComplex32)(cookie, 1., 1.); + EXPECT_DEATH(IONAME(OutputComplex32)(cookie, 1., 1.), + "Internal write overran available records"); + + std::memset(buffer, '\0', bufferSize); + cookie = IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format)); + IONAME(OutputComplex64)(cookie, 1., 1.); + EXPECT_DEATH(IONAME(OutputComplex64)(cookie, 1., 1.), + "Internal write overran available records"); +} + +TEST(TestIOCrash, OverwriteBufferIntegerTest) { + static constexpr int bufferSize{1}; + static char buffer[bufferSize]; + static const char *format{"(I1)"}; + auto *cookie{IONAME(BeginInternalFormattedOutput)( + buffer, bufferSize, format, std::strlen(format))}; + IONAME(OutputInteger64)(cookie, 0xdeadbeef); + ASSERT_DEATH(IONAME(OutputInteger64)(cookie, 0xdeadbeef), + "Internal write overran available records"); +}