| //===----------------------------------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// This file contains the definition of the runTests function, which executes a |
| /// a suite of tests and print a formatted report for each. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef MATHTEST_TESTRUNNER_HPP |
| #define MATHTEST_TESTRUNNER_HPP |
| |
| #include "mathtest/DeviceContext.hpp" |
| #include "mathtest/GpuMathTest.hpp" |
| #include "mathtest/Numerics.hpp" |
| #include "mathtest/TestConfig.hpp" |
| |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/ADT/Twine.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/FormatVariadic.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include <chrono> |
| #include <cstddef> |
| #include <memory> |
| #include <tuple> |
| |
| namespace mathtest { |
| namespace detail { |
| |
| template <auto Func> |
| void printPreamble(const TestConfig &Config, size_t Index, |
| size_t Total) noexcept { |
| using FunctionConfig = FunctionConfig<Func>; |
| |
| llvm::errs() << "[" << (Index + 1) << "/" << Total << "] " |
| << "Running conformance test '" << FunctionConfig::Name |
| << "' with '" << Config.Provider << "' on '" << Config.Platform |
| << "'\n"; |
| llvm::errs().flush(); |
| } |
| |
| template <typename T> |
| void printValue(llvm::raw_ostream &OS, const T &Value) noexcept { |
| if constexpr (IsFloatingPoint_v<T>) { |
| if constexpr (sizeof(T) < sizeof(float)) |
| OS << float(Value); |
| else |
| OS << Value; |
| |
| const FPBits<T> Bits(Value); |
| OS << llvm::formatv(" (0x{0})", llvm::Twine::utohexstr(Bits.uintval())); |
| } else { |
| OS << Value; |
| } |
| } |
| |
| template <typename... Ts> |
| void printValues(llvm::raw_ostream &OS, |
| const std::tuple<Ts...> &ValuesTuple) noexcept { |
| std::apply( |
| [&OS](const auto &...Values) { |
| bool IsFirst = true; |
| auto PrintWithComma = [&](const auto &Value) { |
| if (!IsFirst) |
| OS << ", "; |
| printValue(OS, Value); |
| IsFirst = false; |
| }; |
| (PrintWithComma(Values), ...); |
| }, |
| ValuesTuple); |
| } |
| |
| template <typename TestCaseType> |
| void printWorstFailingCase(llvm::raw_ostream &OS, |
| const TestCaseType &TestCase) noexcept { |
| OS << "--- Worst Failing Case ---\n"; |
| OS << llvm::formatv(" {0,-14} : ", "Input(s)"); |
| printValues(OS, TestCase.Inputs); |
| OS << "\n"; |
| |
| OS << llvm::formatv(" {0,-14} : ", "Actual"); |
| printValue(OS, TestCase.Actual); |
| OS << "\n"; |
| |
| OS << llvm::formatv(" {0,-14} : ", "Expected"); |
| printValue(OS, TestCase.Expected); |
| OS << "\n"; |
| } |
| |
| template <typename TestType, typename ResultType> |
| void printReport(const TestType &Test, const ResultType &Result, |
| const std::chrono::steady_clock::duration &Duration) noexcept { |
| using FunctionConfig = typename TestType::FunctionConfig; |
| |
| const auto Context = Test.getContext(); |
| const auto ElapsedMilliseconds = |
| std::chrono::duration_cast<std::chrono::milliseconds>(Duration).count(); |
| const bool Passed = Result.hasPassed(); |
| |
| llvm::errs() << llvm::formatv("=== Test Report for '{0}' === \n", |
| FunctionConfig::Name); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Provider", |
| Test.getProvider()); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Platform", |
| Context->getPlatform()); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Device", Context->getName()); |
| llvm::errs() << llvm::formatv("{0,-17}: {1} ms\n", "Elapsed time", |
| ElapsedMilliseconds); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "ULP tolerance", |
| FunctionConfig::UlpTolerance); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Max ULP distance", |
| Result.getMaxUlpDistance()); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Test cases", |
| Result.getTestCaseCount()); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Failures", |
| Result.getFailureCount()); |
| llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Status", |
| Passed ? "PASSED" : "FAILED"); |
| |
| if (const auto &Worst = Result.getWorstFailingCase()) |
| printWorstFailingCase(llvm::errs(), Worst.value()); |
| |
| llvm::errs().flush(); |
| } |
| |
| template <auto Func, typename TestType = GpuMathTest<Func>> |
| [[nodiscard]] llvm::Expected<bool> |
| runTest(typename TestType::GeneratorType &Generator, const TestConfig &Config, |
| llvm::StringRef DeviceBinaryDir) { |
| const auto &Platforms = getPlatforms(); |
| |
| if (!llvm::any_of(Platforms, [&](llvm::StringRef Platform) { |
| return Platform.equals_insensitive(Config.Platform); |
| })) |
| return llvm::createStringError("Platform '" + Config.Platform + |
| "' is not available on this system"); |
| |
| auto Context = |
| std::make_shared<DeviceContext>(Config.Platform, /*DeviceId=*/0); |
| auto ExpectedTest = |
| TestType::create(Context, Config.Provider, DeviceBinaryDir); |
| |
| if (!ExpectedTest) |
| return ExpectedTest.takeError(); |
| |
| const auto StartTime = std::chrono::steady_clock::now(); |
| |
| auto Result = ExpectedTest->run(Generator); |
| |
| const auto EndTime = std::chrono::steady_clock::now(); |
| const auto Duration = EndTime - StartTime; |
| |
| printReport(*ExpectedTest, Result, Duration); |
| |
| return Result.hasPassed(); |
| } |
| } // namespace detail |
| |
| template <auto Func, typename TestType = GpuMathTest<Func>> |
| [[nodiscard]] bool runTests(typename TestType::GeneratorType &Generator, |
| const llvm::SmallVector<TestConfig, 4> &Configs, |
| llvm::StringRef DeviceBinaryDir, |
| bool IsVerbose = false) { |
| const size_t NumConfigs = Configs.size(); |
| |
| if (NumConfigs == 0) |
| llvm::errs() << "There is no test configuration to run a test\n"; |
| |
| bool Passed = true; |
| |
| for (const auto &[Index, Config] : llvm::enumerate(Configs)) { |
| detail::printPreamble<Func>(Config, Index, NumConfigs); |
| |
| Generator.reset(); |
| |
| auto ExpectedPassed = |
| detail::runTest<Func, TestType>(Generator, Config, DeviceBinaryDir); |
| |
| if (!ExpectedPassed) { |
| const auto Details = llvm::toString(ExpectedPassed.takeError()); |
| llvm::errs() |
| << "WARNING: Conformance test not supported on this system\n"; |
| |
| if (IsVerbose) |
| llvm::errs() << "Details: " << Details << "\n"; |
| } else { |
| Passed &= *ExpectedPassed; |
| } |
| |
| llvm::errs() << "\n"; |
| } |
| |
| return Passed; |
| } |
| } // namespace mathtest |
| |
| #endif // MATHTEST_TESTRUNNER_HPP |