diff --git a/flang/unittests/CMakeLists.txt b/flang/unittests/CMakeLists.txt --- a/flang/unittests/CMakeLists.txt +++ b/flang/unittests/CMakeLists.txt @@ -40,6 +40,7 @@ add_subdirectory(Decimal) add_subdirectory(Evaluate) add_subdirectory(Runtime) +add_subdirectory(RuntimeGTest) if (FLANG_BUILD_NEW_DRIVER) add_subdirectory(Frontend) diff --git a/flang/unittests/Runtime/CMakeLists.txt b/flang/unittests/Runtime/CMakeLists.txt --- a/flang/unittests/Runtime/CMakeLists.txt +++ b/flang/unittests/Runtime/CMakeLists.txt @@ -18,11 +18,6 @@ ${llvm_libs} ) -add_flang_nongtest_unittest(format - RuntimeTesting - FortranRuntime -) - add_flang_nongtest_unittest(hello RuntimeTesting FortranRuntime @@ -42,11 +37,6 @@ FortranRuntime ) -add_flang_nongtest_unittest(list-input - RuntimeTesting - FortranRuntime -) - add_flang_nongtest_unittest(character RuntimeTesting FortranRuntime diff --git a/flang/unittests/Runtime/list-input.cpp b/flang/unittests/Runtime/list-input.cpp deleted file mode 100644 --- a/flang/unittests/Runtime/list-input.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Basic sanity tests for list-directed input - -#include "testing.h" -#include "../../runtime/descriptor.h" -#include "../../runtime/io-api.h" -#include "../../runtime/io-error.h" -#include -#include - -using namespace Fortran::runtime; -using namespace Fortran::runtime::io; - -int main() { - StartTests(); - - char buffer[4][32]; - int j{0}; - for (const char *p : {"1 2 2*3 ,", ",6,,8,1*", - "2*'abcdefghijklmnopqrstuvwxyzABC", "DEFGHIJKLMNOPQRSTUVWXYZ'"}) { - SetCharacter(buffer[j++], sizeof buffer[0], p); - } - for (; j < 4; ++j) { - SetCharacter(buffer[j], sizeof buffer[0], ""); - } - - StaticDescriptor<1> staticDescriptor; - Descriptor &whole{staticDescriptor.descriptor()}; - SubscriptValue extent[]{4}; - whole.Establish(TypeCode{CFI_type_char}, sizeof buffer[0], &buffer, 1, extent, - CFI_attribute_pointer); - whole.Dump(); - whole.Check(); - - try { - auto cookie{IONAME(BeginInternalArrayListInput)(whole)}; - std::int64_t n[9]{-1, -2, -3, -4, 5, -6, 7, -8, 9}; - std::int64_t want[9]{1, 2, 3, 3, 5, 6, 7, 8, 9}; - for (j = 0; j < 9; ++j) { - IONAME(InputInteger)(cookie, n[j]); - } - char asc[2][54]{}; - IONAME(InputAscii)(cookie, asc[0], sizeof asc[0] - 1); - IONAME(InputAscii)(cookie, asc[1], sizeof asc[1] - 1); - if (auto status{IONAME(EndIoStatement)(cookie)}) { - Fail() << "list-directed input failed, status " - << static_cast(status) << '\n'; - } else { - for (j = 0; j < 9; ++j) { - if (n[j] != want[j]) { - Fail() << "wanted n[" << j << "]==" << want[j] << ", got " << n[j] - << '\n'; - } - } - for (j = 0; j < 2; ++j) { - if (std::strcmp(asc[j], - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ") != 0) { - Fail() << "wanted asc[" << j << "]=alphabets, got '" << asc[j] - << "'\n"; - } - } - } - } catch (const std::string &crash) { - Fail() << "crash: " << crash << '\n'; - } - - return EndTests(); -} diff --git a/flang/unittests/Runtime/testing.h b/flang/unittests/Runtime/testing.h --- a/flang/unittests/Runtime/testing.h +++ b/flang/unittests/Runtime/testing.h @@ -8,11 +8,25 @@ class raw_ostream; } -void StartTests(); +// Number of runtime crashes discoverd by the crash handler registered in the +// `StartTests` function below. +int GetNumRuntimeCrashes(); + +// Registers a callback in the fortran runtime to register errors in tests. +// When `rethrow` is false, you must check for runtime crashes yourself. +void StartTests(bool rethrow = true); + llvm::raw_ostream &Fail(); + int EndTests(); // Defines a CHARACTER object with padding when needed void SetCharacter(char *, std::size_t, const char *); +// Convenience assertion to ensure no crashes from within the fortran runtime +// have occured. +#define ASSERT_NO_CRASHES() \ + ASSERT_EQ(GetNumRuntimeCrashes(), 0) \ + << "Encountered " << GetNumRuntimeCrashes() << " runtime crashes! " + #endif // FORTRAN_TEST_RUNTIME_TESTING_H_ diff --git a/flang/unittests/Runtime/testing.cpp b/flang/unittests/Runtime/testing.cpp --- a/flang/unittests/Runtime/testing.cpp +++ b/flang/unittests/Runtime/testing.cpp @@ -8,6 +8,8 @@ static int failures{0}; +static bool hasRunStartTests{false}; + // Override the Fortran runtime's Crash() for testing purposes [[noreturn]] static void CatchCrash( const char *sourceFile, int sourceLine, const char *message, va_list &ap) { @@ -16,11 +18,30 @@ va_end(ap); llvm::errs() << (sourceFile ? sourceFile : "unknown source file") << '(' << sourceLine << "): CRASH: " << buffer << '\n'; + failures++; throw std::string{buffer}; } -void StartTests() { - Fortran::runtime::Terminator::RegisterCrashHandler(CatchCrash); +static void CatchCrashNoThrow( + 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() << (sourceFile ? sourceFile : "unknown source file") << '(' + << sourceLine << "): CRASH: " << buffer << '\n'; + failures++; +} + +int GetNumRuntimeCrashes() { return failures; } + +void StartTests(bool rethrow /*=true*/) { + if (hasRunStartTests) + return; + if (rethrow) + Fortran::runtime::Terminator::RegisterCrashHandler(CatchCrash); + else + Fortran::runtime::Terminator::RegisterCrashHandler(CatchCrashNoThrow); + hasRunStartTests = true; } llvm::raw_ostream &Fail() { diff --git a/flang/unittests/RuntimeGTest/CMakeLists.txt b/flang/unittests/RuntimeGTest/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/CMakeLists.txt @@ -0,0 +1,14 @@ +add_flang_unittest(FlangRuntimeTests + CharacterTest.cpp + CMakeLists.txt + ExternalIOTest.cpp + FormatTest.cpp + HelloWorldOutputTest.cpp + ListInputTest.cpp + RuntimeTesting.cpp +) + +target_link_libraries(FlangRuntimeTests + PRIVATE + FortranRuntime +) diff --git a/flang/unittests/RuntimeGTest/CharacterTest.cpp b/flang/unittests/RuntimeGTest/CharacterTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/CharacterTest.cpp @@ -0,0 +1,54 @@ +// Basic sanity tests of CHARACTER API; exhaustive testing will be done +// in Fortran. + +#include "../../runtime/character.h" +#include "RuntimeTesting.h" +#include "gtest/gtest.h" +#include + +using namespace Fortran::runtime; + +static void AppendAndPad(std::size_t limit) { + char x[8]; + std::size_t xLen{0}; + std::memset(x, 0, sizeof x); + xLen = RTNAME(CharacterAppend1)(x, limit, xLen, "abc", 3); + xLen = RTNAME(CharacterAppend1)(x, limit, xLen, "DE", 2); + RTNAME(CharacterPad1)(x, limit, xLen); + ASSERT_LE(xLen, limit) << "xLen " << xLen << ">" << limit; + if (x[limit]) { + EXPECT_TRUE(false) << "x[" << limit << "]='" << x[limit] << "'\n"; + x[limit] = '\0'; + } + ASSERT_FALSE(std::memcmp(x, "abcDE ", limit)) << "x = '" << x << "'"; +} + +static void TestCharCompare(const char *x, const char *y, std::size_t xBytes, + std::size_t yBytes, int expect) { + int cmp{RTNAME(CharacterCompareScalar1)(x, y, xBytes, yBytes)}; + char buf[2][8]; + std::memset(buf, 0, sizeof buf); + std::memcpy(buf[0], x, xBytes); + std::memcpy(buf[1], y, yBytes); + ASSERT_EQ(cmp, expect) << "compare '" << buf[0] << "'(" << xBytes << ") to '" + << buf[1] << "'(" << yBytes << "), got " << cmp + << ", should be " << expect << '\n'; +} + +static void Compare(const char *x, const char *y, std::size_t xBytes, + std::size_t yBytes, int expect) { + TestCharCompare(x, y, xBytes, yBytes, expect); + TestCharCompare(y, x, yBytes, xBytes, -expect); +} + +TEST(CharacterTests, CompareCharacters) { + StartTests(); + for (std::size_t j{0}; j < 8; ++j) { + AppendAndPad(j); + } + Compare("abc", "abc", 3, 3, 0); + Compare("abc", "def", 3, 3, -1); + Compare("ab ", "abc", 3, 2, 0); + Compare("abc", "abc", 2, 3, -1); + EndTests(); +} diff --git a/flang/unittests/RuntimeGTest/ExternalIOTest.cpp b/flang/unittests/RuntimeGTest/ExternalIOTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/ExternalIOTest.cpp @@ -0,0 +1,413 @@ +// Sanity test for all external I/O modes + +#include "RuntimeTesting.h" +#include "../../runtime/io-api.h" +#include "../../runtime/main.h" +#include "../../runtime/stop.h" +#include "gtest/gtest.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace Fortran::runtime::io; + +TEST(ExternalIOTests, DirectUnformatted) { + // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',& + // FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)"; + ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)"; + ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)"; + + std::int64_t buffer; + static constexpr std::size_t recl{sizeof buffer}; + ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()"; + ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)"; + + int unit{-1}; + ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit(): unit=" << unit; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenNewUnit"; + + static constexpr int records{10}; + for (int j{1}; j <= records; ++j) { + // WRITE(UNIT=unit,REC=j) j + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + buffer = j; + ASSERT_TRUE(IONAME(OutputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl, recl)) + << "OutputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OutputUnformattedBlock"; + } + + for (int j{records}; j >= 1; --j) { + // READ(UNIT=unit,REC=j) n + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + ASSERT_TRUE(IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl, recl)) + << "InputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputUnformattedBlock"; + ASSERT_EQ(buffer, j) << "Read back " << buffer + << " from direct unformatted record " << j << ", expected " << j; + } + + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Close"; +} + +TEST(ExternalIOTests, DirectUnformattedSwapped) { + // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',& + // FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH',CONVERT='NATIVE') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)"; + ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)"; + ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)"; + ASSERT_TRUE(IONAME(SetConvert)(io, "NATIVE", 6)) << "SetConvert(NATIVE)"; + + std::int64_t buffer; + static constexpr std::size_t recl{sizeof buffer}; + ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()"; + ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)"; + int unit{-1}; + ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()" << "unit=" + << unit; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenNewUnit"; + + static constexpr int records{10}; + for (int j{1}; j <= records; ++j) { + // WRITE(UNIT=unit,REC=j) j + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + buffer = j; + ASSERT_TRUE(IONAME(OutputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl, recl)) + << "OutputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OutputUnformattedBlock"; + } + + // OPEN(UNIT=unit,STATUS='OLD',CONVERT='SWAP') + io = IONAME(BeginOpenUnit)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "OLD", 3)) << "SetStatus(OLD)"; + ASSERT_TRUE(IONAME(SetConvert)(io, "SWAP", 4)) << "SetConvert(SWAP)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenUnit"; + + for (int j{records}; j >= 1; --j) { + // READ(UNIT=unit,REC=j) n + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + ASSERT_TRUE(IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl, recl)) + << "InputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputUnformattedBlock"; + ASSERT_EQ((buffer >> 56), j) << "Read back " << (buffer >> 56) + << " from direct unformatted record " << j << ", expected " << j; + } + + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Close"; +} + +void TestSequentialFixedUnformatted() { + // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',& + // FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10)) + << "SetAccess(SEQUENTIAL)"; + ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)"; + ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)"; + + std::int64_t buffer; + static constexpr std::size_t recl{sizeof buffer}; + ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()"; + ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)"; + int unit{-1}; + ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenNewUnit"; + + static const int records{10}; + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; WRITE(UNIT=unit) j; END DO + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + buffer = j; + ASSERT_TRUE(IONAME(OutputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl, recl)) + << "OutputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OutputUnformattedBlock"; + } + + // REWIND(UNIT=unit) + io = IONAME(BeginRewind)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Rewind"; + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl, recl)) + << "InputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputUnformattedBlock"; + ASSERT_EQ(buffer, j) << "Read back " << buffer + << " from sequential fixed unformatted record " << j + << ", expected " << j; + } + + for (int j{records}; j >= 1; --j) { + // BACKSPACE(UNIT=unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Backspace (before read)"; + // READ(UNIT=unit) n + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), recl, recl)) + << "InputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputUnformattedBlock"; + ASSERT_EQ(buffer, j) << "Read back " << buffer + << " from sequential fixed unformatted record " << j + << " after backspacing, expected " << j; + // BACKSPACE(UNIT=unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Backspace (after read)"; + } + + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Close"; +} + + +TEST(ExternalIOTests, SequentialVariableUnformatted) { + // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',& + // FORM='UNFORMATTED',STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10)) << "SetAccess(SEQUENTIAL)"; + ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)"; + ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)"; + ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)"; + + int unit{-1}; + ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit() unit=" << unit; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenNewUnit"; + + static const int records{10}; + std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)] + for (int j{0}; j < records; ++j) { + buffer[j] = j; + } + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; WRITE(UNIT=unit) BUFFER(0:j); END DO + io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(OutputUnformattedBlock) + (io, reinterpret_cast(&buffer), j * sizeof *buffer, + sizeof *buffer)) + << "OutputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OutputUnformattedBlock"; + } + + // REWIND(UNIT=unit) + io = IONAME(BeginRewind)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Rewind"; + for (int j{1}; j <= records; ++j) { + // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), j * sizeof *buffer, + sizeof *buffer)) << "InputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputUnformattedBlock"; + for (int k{0}; k < j; ++k) { + ASSERT_EQ(buffer[k], k) + << "Read back [" << k << "]=" << buffer[k] + << " from direct unformatted record " << j << ", expected " << k; + } + } + + for (int j{records}; j >= 1; --j) { + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Backspace (before read)"; + // READ(unit=unit) n; check + io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(InputUnformattedBlock) + (io, reinterpret_cast(&buffer), j * sizeof *buffer, + sizeof *buffer)) << "InputUnformattedBlock()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputUnformattedBlock"; + for (int k{0}; k < j; ++k) { + ASSERT_EQ(buffer[k], k) + << "Read back [" << k << "]=" << buffer[k] + << " from sequential variable unformatted record " << j + << ", expected " << k << '\n'; + } + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Backspace (after read)"; + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Close"; +} + +TEST(ExternalIOTests, DirectFormatted) { + // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',& + // FORM='FORMATTED',RECL=8,STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)"; + ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)"; + ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)"; + + static constexpr std::size_t recl{8}; + ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()"; + ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)"; + + int unit{-1}; + ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit() unit=" << unit; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenNewUnit"; + + static constexpr int records{10}; + static const char fmt[]{"(I4)"}; + for (int j{1}; j <= records; ++j) { + // WRITE(UNIT=unit,FMT=fmt,REC=j) j + io = IONAME(BeginExternalFormattedOutput)( + fmt, sizeof fmt - 1, unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + ASSERT_TRUE(IONAME(OutputInteger64)(io, j)) << "OutputInteger64()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for OutputInteger64"; + } + + for (int j{records}; j >= 1; --j) { + // READ(UNIT=unit,FMT=fmt,REC=j) n + io = IONAME(BeginExternalFormattedInput)( + fmt, sizeof fmt - 1, unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')'; + std::int64_t buffer; + ASSERT_TRUE(IONAME(InputInteger)(io, buffer)) << "InputInteger()"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputInteger"; + ASSERT_EQ(buffer, j) << "Read back " << buffer + << " from direct formatted record " << j << ", expected " << j; + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Close"; +} + +TEST(ExternalIOTests, SequentialVariableFormatted) { + // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',& + // FORM='FORMATTED',STATUS='SCRATCH') + auto io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)}; + ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10)) + << "SetAccess(SEQUENTIAL)"; + ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)"; + ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)"; + ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)"; + + int unit{-1}; + ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit() unit=" << unit; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OpenNewUnit"; + static const int records{10}; + std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)] + for (int j{0}; j < records; ++j) { + buffer[j] = j; + } + char fmt[32]; + for (int j{1}; j <= records; ++j) { + std::snprintf(fmt, sizeof fmt, "(%dI4)", j); + // DO J=1,RECORDS; WRITE(UNIT=unit,FMT=fmt) BUFFER(0:j); END DO + io = IONAME(BeginExternalFormattedOutput)( + fmt, std::strlen(fmt), unit, __FILE__, __LINE__); + for (int k{0}; k < j; ++k) { + ASSERT_TRUE(IONAME(OutputInteger64)(io, buffer[k])) + << "OutputInteger64()"; + } + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for OutputInteger64"; + } + + // REWIND(UNIT=unit) + io = IONAME(BeginRewind)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for Rewind"; + for (int j{1}; j <= records; ++j) { + std::snprintf(fmt, sizeof fmt, "(%dI4)", j); + // DO J=1,RECORDS; READ(UNIT=unit,FMT=fmt) n; check n; END DO + io = IONAME(BeginExternalFormattedInput)( + fmt, std::strlen(fmt), unit, __FILE__, __LINE__); + std::int64_t check[records]; + for (int k{0}; k < j; ++k) { + ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()"; + } + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputInteger"; + for (int k{0}; k < j; ++k) { + ASSERT_EQ(buffer[k], check[k]) << "Read back [" << k << "]=" << check[k] + << " from sequential variable formatted record " << j << ", expected " + << buffer[k]; + } + } + + for (int j{records}; j >= 1; --j) { + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Backspace (before read)"; + std::snprintf(fmt, sizeof fmt, "(%dI4)", j); + // READ(UNIT=unit,FMT=fmt) n; check + io = IONAME(BeginExternalFormattedInput)( + fmt, std::strlen(fmt), unit, __FILE__, __LINE__); + std::int64_t check[records]; + for (int k{0}; k < j; ++k) { + ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()"; + } + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for InputInteger"; + for (int k{0}; k < j; ++k) { + ASSERT_EQ(buffer[k], check[k]) << "Read back [" << k << "]=" << buffer[k] + << " from sequential variable formatted record " << j << ", expected " + << buffer[k]; + } + // BACKSPACE(unit) + io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__); + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Backspace (after read)"; + } + // CLOSE(UNIT=unit,STATUS='DELETE') + io = IONAME(BeginClose)(unit, __FILE__, __LINE__); + ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)"; + ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) + << "EndIoStatement() for Close"; +} + +TEST(ExternalIOTests, StreamUnformatted) { + // TODO +} diff --git a/flang/unittests/Runtime/format.cpp b/flang/unittests/RuntimeGTest/FormatTest.cpp rename from flang/unittests/Runtime/format.cpp rename to flang/unittests/RuntimeGTest/FormatTest.cpp --- a/flang/unittests/Runtime/format.cpp +++ b/flang/unittests/RuntimeGTest/FormatTest.cpp @@ -1,11 +1,13 @@ // Tests basic FORMAT string traversal -#include "testing.h" +#include "RuntimeTesting.h" +#include "gtest/gtest.h" #include "../runtime/format-implementation.h" #include "../runtime/io-error.h" #include #include #include +#include #include using namespace Fortran::runtime; @@ -27,6 +29,7 @@ void HandleAbsolutePosition(std::int64_t); void Report(const DataEdit &); void Check(Results &); + Results results; MutableModes &mutableModes() { return mutableModes_; } @@ -89,51 +92,74 @@ } void TestFormatContext::Check(Results &expect) { - if (expect != results) { - Fail() << "expected:"; - for (const std::string &s : expect) { - llvm::errs() << ' ' << s; - } - llvm::errs() << "\ngot:"; - for (const std::string &s : results) { - llvm::errs() << ' ' << s; - } - llvm::errs() << '\n'; + std::stringstream message; + message << "expected:"; + for (const std::string &s : expect) { + message << ' ' << s; + } + message << ", got:"; + for (const std::string &s : results) { + message << ' ' << s; } + EXPECT_EQ(expect, results) << message.str(); expect.clear(); results.clear(); } -static void Test(int n, const char *format, Results &&expect, int repeat = 1) { +// Tuple of types over which the test cases are parameterized +using Params = std::tuple; + +// Fixture which constructs needed components for each test, includeing a +// TestFormatContext +struct FormatTestsFixture : ::testing::TestWithParam { + virtual void SetUp() { + StartTests(); + std::tie(n, format, expect, repeat) = GetParam(); + } + virtual void TearDown() { + ASSERT_NO_CRASHES() << " In teardown of FormatTestsFixture " << __FILE__ + << __LINE__; + EndTests(); + } + +protected: + int n; + const char *format; + Results expect; + int repeat; TestFormatContext context; - FormatControl control{ +}; + +TEST_P(FormatTestsFixture, FormatStringTraversal) { + FormatControl control{ context, format, std::strlen(format)}; - try { - for (int j{0}; j < n; ++j) { - context.Report(control.GetNextDataEdit(context, repeat)); - } - control.Finish(context); - if (int iostat{context.GetIoStat()}) { - context.Crash("GetIoStat() == %d", iostat); - } - } catch (const std::string &crash) { - context.results.push_back("Crash:"s + crash); + + for (int j{0}; j < n; ++j) { + context.Report(/*edit=*/control.GetNextDataEdit(context, repeat)); + ASSERT_NO_CRASHES() << __FILE__ << " " << __LINE__; } + + control.Finish(context); + int iostat{context.GetIoStat()}; + ASSERT_FALSE(iostat) << "GetIoStat() == " << iostat; + context.Check(expect); + ASSERT_NO_CRASHES() << __FILE__ << " " << __LINE__; } -int main() { - StartTests(); - Test(1, "('PI=',F9.7)", Results{"'PI='", "F9.7"}); - Test(1, "(3HPI=F9.7)", Results{"'PI='", "F9.7"}); - Test(1, "(3HPI=/F9.7)", Results{"'PI='", "/", "F9.7"}); - Test(2, "('PI=',F9.7)", Results{"'PI='", "F9.7", "/", "'PI='", "F9.7"}); - Test(2, "(2('PI=',F9.7),'done')", - Results{"'PI='", "F9.7", "'PI='", "F9.7", "'done'"}); - Test(2, "(3('PI=',F9.7,:),'tooFar')", - Results{"'PI='", "F9.7", "'PI='", "F9.7"}); - Test(2, "(*('PI=',F9.7,:),'tooFar')", - Results{"'PI='", "F9.7", "'PI='", "F9.7"}); - Test(1, "(3F9.7)", Results{"2*F9.7"}, 2); - return EndTests(); -} +// Trailing ',' in this macro suppresses a warning which is emmitted even when +// the macro is used correctly +INSTANTIATE_TEST_CASE_P(FormatTests, FormatTestsFixture, + ::testing::Values( + std::make_tuple(1, "('PI=',F9.7)", Results{"'PI='", "F9.7"}, 1), + std::make_tuple(1, "(3HPI=F9.7)", Results{"'PI='", "F9.7"}, 1), + std::make_tuple(1, "(3HPI=/F9.7)", Results{"'PI='", "/", "F9.7"}, 1), + std::make_tuple(2, "('PI=',F9.7)", + Results{"'PI='", "F9.7", "/", "'PI='", "F9.7"}, 1), + std::make_tuple(2, "(2('PI=',F9.7),'done')", + Results{"'PI='", "F9.7", "'PI='", "F9.7", "'done'"}, 1), + std::make_tuple(2, "(3('PI=',F9.7,:),'tooFar')", + Results{"'PI='", "F9.7", "'PI='", "F9.7"}, 1), + std::make_tuple(2, "(*('PI=',F9.7,:),'tooFar')", + Results{"'PI='", "F9.7", "'PI='", "F9.7"}, 1), + std::make_tuple(1, "(3F9.7)", Results{"2*F9.7"}, 2)), ); diff --git a/flang/unittests/RuntimeGTest/HelloWorldOutputTest.cpp b/flang/unittests/RuntimeGTest/HelloWorldOutputTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/HelloWorldOutputTest.cpp @@ -0,0 +1,540 @@ +// Basic sanity tests of I/O API; exhaustive testing will be done in Fortran + +#include "RuntimeTesting.h" +#include "../../runtime/descriptor.h" +#include "../../runtime/io-api.h" +#include +#include +#include + +using namespace Fortran::runtime; +using namespace Fortran::runtime::io; + +static void VerifyFormat(const char *format, const char *expect, std::string &&got) { + std::string want{expect}; + want.resize(got.size(), ' '); + EXPECT_EQ(want, got) << '\'' << format << "' failed. got '" << got + << "', expected '" << want << "'. " << want.size() << ' ' << got.size() + << ' ' << std::string(expect).size(); +} + +static void VerifyRealFormat(const char* format, double x, const char* expect) { + char buffer[800]; + auto cookie{IONAME(BeginInternalFormattedOutput)( + buffer, sizeof buffer, format, std::strlen(format))}; + IONAME(OutputReal64)(cookie, x); + auto status{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE(status) << '\'' << format << "' failed, status " + << static_cast(status); + VerifyFormat(format, expect, std::string{buffer, sizeof buffer}); +} + +static void VerifyRealInputFormat( + const char *format, const char *data, std::uint64_t want) { + auto cookie{IONAME(BeginInternalFormattedInput)( + data, std::strlen(data), format, std::strlen(format))}; + union { + double x; + std::uint64_t raw; + } u; + u.raw = 0; + IONAME(EnableHandlers)(cookie, true, true, true, true, true); + IONAME(InputReal64)(cookie, u.x); + char iomsg[65]; + iomsg[0] = '\0'; + iomsg[sizeof iomsg - 1] = '\0'; + IONAME(GetIoMsg)(cookie, iomsg, sizeof iomsg - 1); + auto status{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE(status) << '\'' << format << "' failed reading '" << data + << "', status " << static_cast(status) << " iomsg '" << iomsg << "'"; + ASSERT_EQ(u.raw, want) << '\'' << format << "' failed reading '" << data + << "', want 0x" << std::hex << want << ", got 0x" << u.raw; +} + +TEST(IOApiTests, HelloWorldOutputTest) { + StartTests(); + char buffer[32]; + const char *format{"(6HHELLO,,A6,2X,I3,1X,'0x',Z8,1X,L1)"}; + auto cookie{IONAME(BeginInternalFormattedOutput)( + buffer, sizeof buffer, format, std::strlen(format))}; + IONAME(OutputAscii)(cookie, "WORLD", 5); + IONAME(OutputInteger64)(cookie, 678); + IONAME(OutputInteger64)(cookie, 0xfeedface); + IONAME(OutputLogical)(cookie, true); + auto status{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE(status) << "hello: '" << format << "' failed, status " + << static_cast(status); + VerifyFormat(format, "HELLO, WORLD 678 0xFEEDFACE T", + std::string{buffer, sizeof buffer}); + EndTests(); +} + +TEST(IOApiTests, MultilineOutputTest) { + StartTests(); + char buffer[5][32]; + StaticDescriptor<1> staticDescriptor[2]; + Descriptor &whole{staticDescriptor[0].descriptor()}; + SubscriptValue extent[]{5}; + whole.Establish(TypeCode{CFI_type_char}, sizeof buffer[0], &buffer, 1, extent, + CFI_attribute_pointer); + whole.Dump(); + whole.Check(); + Descriptor §ion{staticDescriptor[1].descriptor()}; + SubscriptValue lowers[]{0}, uppers[]{4}, strides[]{1}; + section.Establish(whole.type(), whole.ElementBytes(), nullptr, 1, extent, + CFI_attribute_pointer); + + auto error{ + CFI_section(§ion.raw(), &whole.raw(), lowers, uppers, strides)}; + ASSERT_FALSE(error) << "multiline: CFI_section failed: " << error; + + section.Dump(); + section.Check(); + const char *format{ + "('?abcde,',T1,'>',T9,A,TL12,A,TR25,'<'//G0,17X,'abcd',1(2I4))"}; + auto cookie{IONAME(BeginInternalArrayFormattedOutput)( + section, format, std::strlen(format))}; + IONAME(OutputAscii)(cookie, "WORLD", 5); + IONAME(OutputAscii)(cookie, "HELLO", 5); + IONAME(OutputInteger64)(cookie, 789); + for (int j{666}; j <= 999; j += 111) { + IONAME(OutputInteger64)(cookie, j); + } + + auto status{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE(status) << "multiline: '" << format << "' failed, status " + << static_cast(status); + VerifyFormat(format, + ">HELLO, WORLD <" + " " + "789 abcd 666 777" + " 888 999 " + " ", + std::string{buffer[0], sizeof buffer}); + EndTests(); +} + +TEST(IOApiTests, ListInputTest) { + StartTests(); + static const char input[]{",1*,(5.,6..)"}; + auto cookie{IONAME(BeginInternalListInput)(input, sizeof input - 1)}; + float z[6]; + for (int j{0}; j < 6; ++j) { + z[j] = -(j + 1); + } + for (int j{0}; j < 6; j += 2) { + ASSERT_TRUE(IONAME(InputComplex32)(cookie, &z[j])) + << "InputComplex32 failed\n"; + } + + auto status{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE(status) << "Failed complex list-directed input, status " + << static_cast(status); + + char output[33]; + output[32] = '\0'; + cookie = IONAME(BeginInternalListOutput)(output, 32); + for (int j{0}; j < 6; j += 2) { + ASSERT_TRUE(IONAME(OutputComplex32)(cookie, z[j], z[j + 1])) + << "OutputComplex32 failed"; + } + + status = IONAME(EndIoStatement)(cookie); + static const char expect[33]{" (-1.,-2.) (-3.,-4.) (5.,6.) "}; + ASSERT_FALSE(status) << "Failed complex list-directed output, status " + << static_cast(status); + ASSERT_EQ(std::strncmp(output, expect, 33), 0) + << "Failed complex list-directed output, expected '" << expect + << "', but got '" << output << "'"; + EndTests(); +} + +TEST(IOApiTests, DescriptorOutputTest) { + StartTests(); + char buffer[9]; + // Formatted + const char *format{"(2A4)"}; + auto cookie{IONAME(BeginInternalFormattedOutput)( + buffer, sizeof buffer, format, std::strlen(format))}; + StaticDescriptor<1> staticDescriptor; + Descriptor &desc{staticDescriptor.descriptor()}; + SubscriptValue extent[]{2}; + char data[2][4]; + std::memcpy(data[0], "ABCD", 4); + std::memcpy(data[1], "EFGH", 4); + desc.Establish(TypeCode{CFI_type_char}, sizeof data[0], &data, 1, extent); + desc.Dump(); + desc.Check(); + IONAME(OutputDescriptor)(cookie, desc); + + auto formatStatus{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE(formatStatus) << "descrOutputTest: '" << format << "' failed, status " + << static_cast(formatStatus); + VerifyFormat("descrOutputTest(formatted)", "ABCDEFGH ", + std::string{buffer, sizeof buffer}); + + // List-directed + cookie = IONAME(BeginInternalListOutput)(buffer, sizeof buffer); + IONAME(OutputDescriptor)(cookie, desc); + auto listDirectedStatus{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE(listDirectedStatus) << "descrOutputTest: list-directed failed, status " + << static_cast(listDirectedStatus); + VerifyFormat("descrOutputTest(list)", " ABCDEFGH", + std::string{buffer, sizeof buffer}); + EndTests(); +} + +TEST(IOApiTests, FormatZeroes) { + StartTests(); + static constexpr std::pair zeroes[] { + {"(E32.17,';')", " 0.00000000000000000E+00;"}, + {"(F32.17,';')", " 0.00000000000000000;"}, + {"(G32.17,';')", " 0.0000000000000000 ;"}, + {"(DC,E32.17,';')", " 0,00000000000000000E+00;"}, + {"(DC,F32.17,';')", " 0,00000000000000000;"}, + {"(DC,G32.17,';')", " 0,0000000000000000 ;"}, + {"(D32.17,';')", " 0.00000000000000000D+00;"}, + {"(E32.17E1,';')", " 0.00000000000000000E+0;"}, + {"(G32.17E1,';')", " 0.0000000000000000 ;"}, + {"(E32.17E0,';')", " 0.00000000000000000E+0;"}, + {"(G32.17E0,';')", " 0.0000000000000000 ;"}, + {"(1P,E32.17,';')", " 0.00000000000000000E+00;"}, + {"(1PE32.17,';')", " 0.00000000000000000E+00;"}, // no comma + {"(1P,F32.17,';')", " 0.00000000000000000;"}, + {"(1P,G32.17,';')", " 0.0000000000000000 ;"}, + {"(2P,E32.17,';')", " 00.0000000000000000E+00;"}, + {"(-1P,E32.17,';')", " 0.00000000000000000E+00;"}, + {"(G0,';')", "0.;"}, + }; + + for(auto const& [format, expect] : zeroes) { + VerifyRealFormat(format, 0.0, expect); + } + EndTests(); +} + +TEST(IOApiTests, FormatOnes) { + StartTests(); + static constexpr std::pair ones[] { + {"(E32.17,';')", " 0.10000000000000000E+01;"}, + {"(F32.17,';')", " 1.00000000000000000;"}, + {"(G32.17,';')", " 1.0000000000000000 ;"}, + {"(E32.17E1,';')", " 0.10000000000000000E+1;"}, + {"(G32.17E1,';')", " 1.0000000000000000 ;"}, + {"(E32.17E0,';')", " 0.10000000000000000E+1;"}, + {"(G32.17E0,';')", " 1.0000000000000000 ;"}, + {"(E32.17E4,';')", " 0.10000000000000000E+0001;"}, + {"(G32.17E4,';')", " 1.0000000000000000 ;"}, + {"(1P,E32.17,';')", " 1.00000000000000000E+00;"}, + {"(1PE32.17,';')", " 1.00000000000000000E+00;"}, // no comma + {"(1P,F32.17,';')", " 10.00000000000000000;"}, + {"(1P,G32.17,';')", " 1.0000000000000000 ;"}, + {"(ES32.17,';')", " 1.00000000000000000E+00;"}, + {"(2P,E32.17,';')", " 10.0000000000000000E-01;"}, + {"(2P,G32.17,';')", " 1.0000000000000000 ;"}, + {"(-1P,E32.17,';')", " 0.01000000000000000E+02;"}, + {"(-1P,G32.17,';')", " 1.0000000000000000 ;"}, + {"(G0,';')", "1.;"}, + }; + + for(auto const& [format, expect] : ones) { + VerifyRealFormat(format, 1.0, expect); + } + EndTests(); +} + +TEST(IOApiTests, FormatNegativeOnes) { + StartTests(); + static constexpr std::tuple negOnes[] { + {"(E32.17,';')", " -0.10000000000000000E+01;"}, + {"(F32.17,';')", " -1.00000000000000000;"}, + {"(G32.17,';')", " -1.0000000000000000 ;"}, + {"(G0,';')", "-1.;"}, + }; + for(auto const& [format, expect] : negOnes) { + VerifyRealFormat(format, -1.0, expect); + } + EndTests(); +} + +TEST(IOApiTests, FormatDoubleValues) { + StartTests(); + + volatile union { + double d; + std::uint64_t n; + } u; + + u.n = 0x8000000000000000; // -0 + VerifyRealFormat("(E9.1,';')", u.d, " -0.0E+00;"); + VerifyRealFormat("(F4.0,';')", u.d, " -0.;"); + VerifyRealFormat("(G8.0,';')", u.d, "-0.0E+00;"); + VerifyRealFormat("(G8.1,';')", u.d, " -0. ;"); + VerifyRealFormat("(G0,';')", u.d, "-0.;"); + VerifyRealFormat("(E9.1,';')", u.d, " -0.0E+00;"); + u.n = 0x7ff0000000000000; // +Inf + VerifyRealFormat("(E9.1,';')", u.d, " Inf;"); + VerifyRealFormat("(F9.1,';')", u.d, " Inf;"); + VerifyRealFormat("(G9.1,';')", u.d, " Inf;"); + VerifyRealFormat("(SP,E9.1,';')", u.d, " +Inf;"); + VerifyRealFormat("(SP,F9.1,';')", u.d, " +Inf;"); + VerifyRealFormat("(SP,G9.1,';')", u.d, " +Inf;"); + VerifyRealFormat("(G0,';')", u.d, "Inf;"); + u.n = 0xfff0000000000000; // -Inf + VerifyRealFormat("(E9.1,';')", u.d, " -Inf;"); + VerifyRealFormat("(F9.1,';')", u.d, " -Inf;"); + VerifyRealFormat("(G9.1,';')", u.d, " -Inf;"); + VerifyRealFormat("(G0,';')", u.d, "-Inf;"); + u.n = 0x7ff0000000000001; // NaN + VerifyRealFormat("(E9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(F9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(G9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(G0,';')", u.d, "NaN;"); + u.n = 0xfff0000000000001; // NaN (sign irrelevant) + VerifyRealFormat("(E9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(F9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(G9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(SP,E9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(SP,F9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(SP,G9.1,';')", u.d, " NaN;"); + VerifyRealFormat("(G0,';')", u.d, "NaN;"); + + u.n = 0x3fb999999999999a; // 0.1 rounded + VerifyRealFormat("(E62.55,';')", u.d, + " 0.1000000000000000055511151231257827021181583404541015625E+00;"); + VerifyRealFormat("(E0.0,';')", u.d, "0.E+00;"); + VerifyRealFormat("(E0.55,';')", u.d, + "0.1000000000000000055511151231257827021181583404541015625E+00;"); + VerifyRealFormat("(E0,';')", u.d, ".1E+00;"); + VerifyRealFormat("(F58.55,';')", u.d, + " 0.1000000000000000055511151231257827021181583404541015625;"); + VerifyRealFormat("(F0.0,';')", u.d, "0.;"); + VerifyRealFormat("(F0.55,';')", u.d, + ".1000000000000000055511151231257827021181583404541015625;"); + VerifyRealFormat("(F0,';')", u.d, ".1;"); + VerifyRealFormat("(G62.55,';')", u.d, + " 0.1000000000000000055511151231257827021181583404541015625 ;"); + VerifyRealFormat("(G0.0,';')", u.d, "0.;"); + VerifyRealFormat("(G0.55,';')", u.d, + ".1000000000000000055511151231257827021181583404541015625;"); + VerifyRealFormat("(G0,';')", u.d, ".1;"); + + u.n = 0x3ff8000000000000; // 1.5 + VerifyRealFormat("(E9.2,';')", u.d, " 0.15E+01;"); + VerifyRealFormat("(F4.1,';')", u.d, " 1.5;"); + VerifyRealFormat("(G7.1,';')", u.d, " 2. ;"); + VerifyRealFormat("(RN,E8.1,';')", u.d, " 0.2E+01;"); + VerifyRealFormat("(RN,F3.0,';')", u.d, " 2.;"); + VerifyRealFormat("(RN,G7.0,';')", u.d, " 0.E+01;"); + VerifyRealFormat("(RN,G7.1,';')", u.d, " 2. ;"); + VerifyRealFormat("(RD,E8.1,';')", u.d, " 0.1E+01;"); + VerifyRealFormat("(RD,F3.0,';')", u.d, " 1.;"); + VerifyRealFormat("(RD,G7.0,';')", u.d, " 0.E+01;"); + VerifyRealFormat("(RD,G7.1,';')", u.d, " 1. ;"); + VerifyRealFormat("(RU,E8.1,';')", u.d, " 0.2E+01;"); + VerifyRealFormat("(RU,G7.0,';')", u.d, " 0.E+01;"); + VerifyRealFormat("(RU,G7.1,';')", u.d, " 2. ;"); + VerifyRealFormat("(RZ,E8.1,';')", u.d, " 0.1E+01;"); + VerifyRealFormat("(RZ,F3.0,';')", u.d, " 1.;"); + VerifyRealFormat("(RZ,G7.0,';')", u.d, " 0.E+01;"); + VerifyRealFormat("(RZ,G7.1,';')", u.d, " 1. ;"); + VerifyRealFormat("(RC,E8.1,';')", u.d, " 0.2E+01;"); + VerifyRealFormat("(RC,F3.0,';')", u.d, " 2.;"); + VerifyRealFormat("(RC,G7.0,';')", u.d, " 0.E+01;"); + VerifyRealFormat("(RC,G7.1,';')", u.d, " 2. ;"); + + // TODO continue F and G editing tests on these data + + u.n = 0xbff8000000000000; // -1.5 + VerifyRealFormat("(E9.2,';')", u.d, "-0.15E+01;"); + VerifyRealFormat("(RN,E8.1,';')", u.d, "-0.2E+01;"); + VerifyRealFormat("(RD,E8.1,';')", u.d, "-0.2E+01;"); + VerifyRealFormat("(RU,E8.1,';')", u.d, "-0.1E+01;"); + VerifyRealFormat("(RZ,E8.1,';')", u.d, "-0.1E+01;"); + VerifyRealFormat("(RC,E8.1,';')", u.d, "-0.2E+01;"); + + u.n = 0x4004000000000000; // 2.5 + VerifyRealFormat("(E9.2,';')", u.d, " 0.25E+01;"); + VerifyRealFormat("(RN,E8.1,';')", u.d, " 0.2E+01;"); + VerifyRealFormat("(RD,E8.1,';')", u.d, " 0.2E+01;"); + VerifyRealFormat("(RU,E8.1,';')", u.d, " 0.3E+01;"); + VerifyRealFormat("(RZ,E8.1,';')", u.d, " 0.2E+01;"); + VerifyRealFormat("(RC,E8.1,';')", u.d, " 0.3E+01;"); + + u.n = 0xc004000000000000; // -2.5 + VerifyRealFormat("(E9.2,';')", u.d, "-0.25E+01;"); + VerifyRealFormat("(RN,E8.1,';')", u.d, "-0.2E+01;"); + VerifyRealFormat("(RD,E8.1,';')", u.d, "-0.3E+01;"); + VerifyRealFormat("(RU,E8.1,';')", u.d, "-0.2E+01;"); + VerifyRealFormat("(RZ,E8.1,';')", u.d, "-0.2E+01;"); + VerifyRealFormat("(RC,E8.1,';')", u.d, "-0.3E+01;"); + + u.n = 1; // least positive nonzero subnormal + VerifyRealFormat("(E32.17,';')", u.d, " 0.49406564584124654-323;"); + VerifyRealFormat("(ES32.17,';')", u.d, " 4.94065645841246544-324;"); + VerifyRealFormat("(EN32.17,';')", u.d, " 4.94065645841246544-324;"); + VerifyRealFormat("(E759.752,';')", u.d, + " 0." + "494065645841246544176568792868221372365059802614324764425585682500675507" + "270208751865299836361635992379796564695445717730926656710355939796398774" + "796010781878126300713190311404527845817167848982103688718636056998730723" + "050006387409153564984387312473397273169615140031715385398074126238565591" + "171026658556686768187039560310624931945271591492455329305456544401127480" + "129709999541931989409080416563324524757147869014726780159355238611550134" + "803526493472019379026810710749170333222684475333572083243193609238289345" + "836806010601150616980975307834227731832924790498252473077637592724787465" + "608477820373446969953364701797267771758512566055119913150489110145103786" + "273816725095583738973359899366480994116420570263709027924276754456522908" + "75386825064197182655334472656250-323;"); + VerifyRealFormat("(G0,';')", u.d, ".5-323;"); + VerifyRealFormat("(E757.750,';')", u.d, + " 0." + "494065645841246544176568792868221372365059802614324764425585682500675507" + "270208751865299836361635992379796564695445717730926656710355939796398774" + "796010781878126300713190311404527845817167848982103688718636056998730723" + "050006387409153564984387312473397273169615140031715385398074126238565591" + "171026658556686768187039560310624931945271591492455329305456544401127480" + "129709999541931989409080416563324524757147869014726780159355238611550134" + "803526493472019379026810710749170333222684475333572083243193609238289345" + "836806010601150616980975307834227731832924790498252473077637592724787465" + "608477820373446969953364701797267771758512566055119913150489110145103786" + "273816725095583738973359899366480994116420570263709027924276754456522908" + "753868250641971826553344726562-323;"); + VerifyRealFormat("(RN,E757.750,';')", u.d, + " 0." + "494065645841246544176568792868221372365059802614324764425585682500675507" + "270208751865299836361635992379796564695445717730926656710355939796398774" + "796010781878126300713190311404527845817167848982103688718636056998730723" + "050006387409153564984387312473397273169615140031715385398074126238565591" + "171026658556686768187039560310624931945271591492455329305456544401127480" + "129709999541931989409080416563324524757147869014726780159355238611550134" + "803526493472019379026810710749170333222684475333572083243193609238289345" + "836806010601150616980975307834227731832924790498252473077637592724787465" + "608477820373446969953364701797267771758512566055119913150489110145103786" + "273816725095583738973359899366480994116420570263709027924276754456522908" + "753868250641971826553344726562-323;"); + VerifyRealFormat("(RD,E757.750,';')", u.d, + " 0." + "494065645841246544176568792868221372365059802614324764425585682500675507" + "270208751865299836361635992379796564695445717730926656710355939796398774" + "796010781878126300713190311404527845817167848982103688718636056998730723" + "050006387409153564984387312473397273169615140031715385398074126238565591" + "171026658556686768187039560310624931945271591492455329305456544401127480" + "129709999541931989409080416563324524757147869014726780159355238611550134" + "803526493472019379026810710749170333222684475333572083243193609238289345" + "836806010601150616980975307834227731832924790498252473077637592724787465" + "608477820373446969953364701797267771758512566055119913150489110145103786" + "273816725095583738973359899366480994116420570263709027924276754456522908" + "753868250641971826553344726562-323;"); + VerifyRealFormat("(RU,E757.750,';')", u.d, + " 0." + "494065645841246544176568792868221372365059802614324764425585682500675507" + "270208751865299836361635992379796564695445717730926656710355939796398774" + "796010781878126300713190311404527845817167848982103688718636056998730723" + "050006387409153564984387312473397273169615140031715385398074126238565591" + "171026658556686768187039560310624931945271591492455329305456544401127480" + "129709999541931989409080416563324524757147869014726780159355238611550134" + "803526493472019379026810710749170333222684475333572083243193609238289345" + "836806010601150616980975307834227731832924790498252473077637592724787465" + "608477820373446969953364701797267771758512566055119913150489110145103786" + "273816725095583738973359899366480994116420570263709027924276754456522908" + "753868250641971826553344726563-323;"); + VerifyRealFormat("(RC,E757.750,';')", u.d, + " 0." + "494065645841246544176568792868221372365059802614324764425585682500675507" + "270208751865299836361635992379796564695445717730926656710355939796398774" + "796010781878126300713190311404527845817167848982103688718636056998730723" + "050006387409153564984387312473397273169615140031715385398074126238565591" + "171026658556686768187039560310624931945271591492455329305456544401127480" + "129709999541931989409080416563324524757147869014726780159355238611550134" + "803526493472019379026810710749170333222684475333572083243193609238289345" + "836806010601150616980975307834227731832924790498252473077637592724787465" + "608477820373446969953364701797267771758512566055119913150489110145103786" + "273816725095583738973359899366480994116420570263709027924276754456522908" + "753868250641971826553344726563-323;"); + + u.n = 0x10000000000000; // least positive nonzero normal + VerifyRealFormat("(E723.716,';')", u.d, + " 0." + "222507385850720138309023271733240406421921598046233183055332741688720443" + "481391819585428315901251102056406733973103581100515243416155346010885601" + "238537771882113077799353200233047961014744258363607192156504694250373420" + "837525080665061665815894872049117996859163964850063590877011830487479978" + "088775374994945158045160505091539985658247081864511353793580499211598108" + "576605199243335211435239014879569960959128889160299264151106346631339366" + "347758651302937176204732563178148566435087212282863764204484681140761391" + "147706280168985324411002416144742161856716615054015428508471675290190316" + "132277889672970737312333408698898317506783884692609277397797285865965494" + "10913690954061364675687023986783152906809846172109246253967285156250-" + "307;"); + VerifyRealFormat("(G0,';')", u.d, ".22250738585072014-307;"); + + u.n = 0x7fefffffffffffffuLL; // greatest finite + VerifyRealFormat("(E32.17,';')", u.d, " 0.17976931348623157+309;"); + VerifyRealFormat("(E317.310,';')", u.d, + " 0." + "179769313486231570814527423731704356798070567525844996598917476803157260" + "780028538760589558632766878171540458953514382464234321326889464182768467" + "546703537516986049910576551282076245490090389328944075868508455133942304" + "583236903222948165808559332123348274797826204144723168738177180919299881" + "2504040261841248583680+309;"); + VerifyRealFormat("(ES317.310,';')", u.d, + " 1." + "797693134862315708145274237317043567980705675258449965989174768031572607" + "800285387605895586327668781715404589535143824642343213268894641827684675" + "467035375169860499105765512820762454900903893289440758685084551339423045" + "832369032229481658085593321233482747978262041447231687381771809192998812" + "5040402618412485836800+308;"); + VerifyRealFormat("(EN319.310,';')", u.d, + " 179." + "769313486231570814527423731704356798070567525844996598917476803157260780" + "028538760589558632766878171540458953514382464234321326889464182768467546" + "703537516986049910576551282076245490090389328944075868508455133942304583" + "236903222948165808559332123348274797826204144723168738177180919299881250" + "4040261841248583680000+306;"); + VerifyRealFormat("(G0,';')", u.d, ".17976931348623157+309;"); + + VerifyRealFormat("(F5.3,';')", 25., "*****;"); + VerifyRealFormat("(F5.3,';')", 2.5, "2.500;"); + VerifyRealFormat("(F5.3,';')", 0.25, "0.250;"); + VerifyRealFormat("(F5.3,';')", 0.025, "0.025;"); + VerifyRealFormat("(F5.3,';')", 0.0025, "0.003;"); + VerifyRealFormat("(F5.3,';')", 0.00025, "0.000;"); + VerifyRealFormat("(F5.3,';')", 0.000025, "0.000;"); + VerifyRealFormat("(F5.3,';')", -25., "*****;"); + VerifyRealFormat("(F5.3,';')", -2.5, "*****;"); + VerifyRealFormat("(F5.3,';')", -0.25, "-.250;"); + VerifyRealFormat("(F5.3,';')", -0.025, "-.025;"); + VerifyRealFormat("(F5.3,';')", -0.0025, "-.003;"); + VerifyRealFormat("(F5.3,';')", -0.00025, "-.000;"); + VerifyRealFormat("(F5.3,';')", -0.000025, "-.000;"); + EndTests(); +} + +TEST(IOApiTests, FormatDoubleInputValues) { + StartTests(); + VerifyRealInputFormat("(F18.0)", " 0", 0x0); + VerifyRealInputFormat("(F18.0)", " ", 0x0); + VerifyRealInputFormat("(F18.0)", " -0", 0x8000000000000000); + VerifyRealInputFormat("(F18.0)", " 01", 0x3ff0000000000000); + VerifyRealInputFormat("(F18.0)", " 1", 0x3ff0000000000000); + VerifyRealInputFormat("(F18.0)", " 125.", 0x405f400000000000); + VerifyRealInputFormat("(F18.0)", " 12.5", 0x4029000000000000); + VerifyRealInputFormat("(F18.0)", " 1.25", 0x3ff4000000000000); + VerifyRealInputFormat("(F18.0)", " 01.25", 0x3ff4000000000000); + VerifyRealInputFormat("(F18.0)", " .125", 0x3fc0000000000000); + VerifyRealInputFormat("(F18.0)", " 0.125", 0x3fc0000000000000); + VerifyRealInputFormat("(F18.0)", " .0625", 0x3fb0000000000000); + VerifyRealInputFormat("(F18.0)", " 0.0625", 0x3fb0000000000000); + VerifyRealInputFormat("(F18.0)", " 125", 0x405f400000000000); + VerifyRealInputFormat("(F18.1)", " 125", 0x4029000000000000); + VerifyRealInputFormat("(F18.2)", " 125", 0x3ff4000000000000); + VerifyRealInputFormat("(F18.3)", " 125", 0x3fc0000000000000); + VerifyRealInputFormat( + "(-1P,F18.0)", " 125", 0x4093880000000000); // 1250 + VerifyRealInputFormat("(1P,F18.0)", " 125", 0x4029000000000000); // 12.5 + VerifyRealInputFormat("(BZ,F18.0)", " 125 ", 0x4093880000000000); // 1250 + VerifyRealInputFormat("(BZ,F18.0)", " 125 . e +1 ", 0x42a6bcc41e900000); // 1.25e13 + VerifyRealInputFormat("(DC,F18.0)", " 12,5", 0x4029000000000000); + EndTests(); +} diff --git a/flang/unittests/RuntimeGTest/ListInputTest.cpp b/flang/unittests/RuntimeGTest/ListInputTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/ListInputTest.cpp @@ -0,0 +1,64 @@ +// Basic sanity tests for list-directed input + +#include "RuntimeTesting.h" +#include "gtest/gtest.h" +#include "../../runtime/descriptor.h" +#include "../../runtime/io-api.h" +#include "../../runtime/io-error.h" +#include +#include + +using namespace Fortran::runtime; +using namespace Fortran::runtime::io; + +TEST(InputTest, ListInput) { + StartTests(); + char buffer[4][32]; + int j{0}; + for (const char *p : {"1 2 2*3 ,", ",6,,8,1*", + "2*'abcdefghijklmnopqrstuvwxyzABC", "DEFGHIJKLMNOPQRSTUVWXYZ'"}) { + SetCharacter(buffer[j++], sizeof buffer[0], p); + } + for (; j < 4; ++j) { + SetCharacter(buffer[j], sizeof buffer[0], ""); + } + + StaticDescriptor<1> staticDescriptor; + Descriptor &whole{staticDescriptor.descriptor()}; + SubscriptValue extent[]{4}; + whole.Establish(TypeCode{CFI_type_char}, sizeof buffer[0], &buffer, 1, extent, + CFI_attribute_pointer); + whole.Dump(); + whole.Check(); + + auto cookie{IONAME(BeginInternalArrayListInput)(whole)}; + std::int64_t n[9]{-1, -2, -3, -4, 5, -6, 7, -8, 9}; + std::int64_t want[9]{1, 2, 3, 3, 5, 6, 7, 8, 9}; + for (j = 0; j < 9; ++j) { + IONAME(InputInteger)(cookie, n[j]); + } + + char asc[2][54]{}; + IONAME(InputAscii)(cookie, asc[0], sizeof asc[0] - 1); + IONAME(InputAscii)(cookie, asc[1], sizeof asc[1] - 1); + + auto status{IONAME(EndIoStatement)(cookie)}; + ASSERT_FALSE((bool)status) << "list-directed input failed, status " + << static_cast(status) << '\n'; + ASSERT_NO_CRASHES() << __FILE__ << ' ' << __LINE__; + + for (j = 0; j < 9; ++j) { + ASSERT_EQ(n[j], want[j]) + << "wanted n[" << j << "]==" << want[j] << ", got " << n[j] << '\n'; + } + ASSERT_NO_CRASHES() << __FILE__ << ' ' << __LINE__; + + for (j = 0; j < 2; ++j) { + ASSERT_EQ(std::strcmp(asc[j], + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "), + 0) + << "wanted asc[" << j << "]=alphabets, got '" << asc[j] << "'\n"; + } + ASSERT_NO_CRASHES() << __FILE__ << ' ' << __LINE__; + EndTests(); +} diff --git a/flang/unittests/RuntimeGTest/RuntimeTesting.h b/flang/unittests/RuntimeGTest/RuntimeTesting.h new file mode 100644 --- /dev/null +++ b/flang/unittests/RuntimeGTest/RuntimeTesting.h @@ -0,0 +1,27 @@ +#ifndef FORTRAN_TEST_RUNTIME_TESTING_H_ +#define FORTRAN_TEST_RUNTIME_TESTING_H_ + +#include "gtest/gtest.h" +#include + +// Number of runtime crashes discoverd by the crash handler registered in the +// `StartTests` function below. +int GetNumRuntimeCrashes(); + +// Registers a callback in the fortran runtime to register errors in tests. +// When `rethrow` is false, you must check for runtime crashes yourself. +void StartTests(); + +// Resets static variables that should not carry over between tests +void EndTests(); + +// Defines a CHARACTER object with padding when needed +void SetCharacter(char *, std::size_t, const char *); + +// Convenience assertion to ensure no crashes from within the fortran runtime +// have occured. +#define ASSERT_NO_CRASHES() \ + ASSERT_EQ(GetNumRuntimeCrashes(), 0) \ + << "Encountered " << GetNumRuntimeCrashes() << " runtime crashes! " + +#endif // FORTRAN_TEST_RUNTIME_TESTING_H_ diff --git a/flang/unittests/Runtime/testing.cpp b/flang/unittests/RuntimeGTest/RuntimeTesting.cpp copy from flang/unittests/Runtime/testing.cpp copy to flang/unittests/RuntimeGTest/RuntimeTesting.cpp --- a/flang/unittests/Runtime/testing.cpp +++ b/flang/unittests/RuntimeGTest/RuntimeTesting.cpp @@ -1,4 +1,4 @@ -#include "testing.h" +#include "RuntimeTesting.h" #include "../../runtime/terminator.h" #include #include @@ -8,33 +8,31 @@ static int failures{0}; +static bool hasRunStartTests{false}; + // Override the Fortran runtime's Crash() for testing purposes -[[noreturn]] static void CatchCrash( +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() << (sourceFile ? sourceFile : "unknown source file") << '(' << sourceLine << "): CRASH: " << buffer << '\n'; - throw std::string{buffer}; + failures++; } +int GetNumRuntimeCrashes() { return failures; } + void StartTests() { + if (hasRunStartTests) + return; Fortran::runtime::Terminator::RegisterCrashHandler(CatchCrash); + hasRunStartTests = true; } -llvm::raw_ostream &Fail() { - ++failures; - return llvm::errs(); -} - -int EndTests() { - if (failures == 0) { - llvm::outs() << "PASS\n"; - } else { - llvm::outs() << "FAIL " << failures << " tests\n"; - } - return failures != 0; +void EndTests() { + ASSERT_NO_CRASHES(); + failures = 0; } void SetCharacter(char *to, std::size_t n, const char *from) {