diff --git a/include/mqt-core/qdmi/devices/sc/Configuration.hpp b/include/mqt-core/qdmi/devices/sc/Configuration.hpp index 66445fbbe7..5b0411700b 100644 --- a/include/mqt-core/qdmi/devices/sc/Configuration.hpp +++ b/include/mqt-core/qdmi/devices/sc/Configuration.hpp @@ -16,7 +16,6 @@ #include "llvm/Support/Error.h" #include -#include #include #include #include @@ -75,8 +74,7 @@ struct Device { /// Parse and validate a device description and report errors with @p source. [[nodiscard]] llvm::Expected readJSON(std::string_view json, std::string_view source); -[[nodiscard]] llvm::Expected -readJSON(std::istream& stream, std::string_view source = "input"); +/// Read a JSON file and return validation or file-access errors. [[nodiscard]] llvm::Expected readJSON(const std::string& path); } // namespace sc diff --git a/src/qdmi/common/DeviceConfiguration.cpp b/src/qdmi/common/DeviceConfiguration.cpp index 31e619e678..3b04ed7308 100644 --- a/src/qdmi/common/DeviceConfiguration.cpp +++ b/src/qdmi/common/DeviceConfiguration.cpp @@ -10,27 +10,24 @@ #include "qdmi/common/DeviceConfiguration.hpp" +#include "qdmi/common/Common.hpp" #include "qdmi/common/Diagnostics.hpp" #include "qdmi/device.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Process.h" -#include #include #include #include #include -#include -#include -#include #include #include #include #include #include -#include #ifdef _WIN32 #include @@ -103,77 +100,27 @@ namespace qdmi::detail { } namespace { -template -void reportPath(const std::filesystem::path& path, - const std::format_string<> fallback, - Reporter&& reporter) noexcept { - try { -#ifdef _WIN32 - const auto pathText = path.string(); - std::forward(reporter)(std::string_view(pathText)); -#else - std::forward(reporter)(std::string_view(path.native())); -#endif - } catch (...) { - qdmi::diagnostics::error(fallback); - } -} - [[nodiscard]] std::optional readFile(const std::filesystem::path& path, const bool bundled, int& status) { - std::error_code error; - const auto exists = std::filesystem::exists(path, error); - if (error) { - status = bundled ? QDMI_ERROR_FATAL : QDMI_ERROR_PERMISSIONDENIED; - reportPath(path, - "Cannot inspect QDMI device configuration (details unavailable)", - [&error](const std::string_view pathText) { - const auto message = error.message(); - qdmi::diagnostics::error( - "Cannot inspect QDMI device configuration '{}': {}", - pathText, message); - }); - return std::nullopt; - } - if (!exists) { - status = bundled ? QDMI_ERROR_FATAL : QDMI_ERROR_NOTFOUND; - reportPath(path, "QDMI device configuration does not exist", - [](const std::string_view pathText) { - qdmi::diagnostics::error( - "QDMI device configuration '{}' does not exist", pathText); - }); - return std::nullopt; - } - errno = 0; - std::ifstream input(path, std::ios::binary); - if (!input) { - status = bundled ? QDMI_ERROR_FATAL : QDMI_ERROR_PERMISSIONDENIED; - const auto errorNumber = errno; - reportPath(path, "Cannot read QDMI device configuration", - [errorNumber](const std::string_view pathText) { - const auto* const message = std::strerror(errorNumber); - qdmi::diagnostics::error( - "Cannot read QDMI device configuration '{}': {}", pathText, - message == nullptr ? "unknown error" : message); - }); - return std::nullopt; - } - std::string json{std::istreambuf_iterator(input), - std::istreambuf_iterator()}; - if (!input.eof() && input.fail()) { - status = bundled ? QDMI_ERROR_FATAL : QDMI_ERROR_PERMISSIONDENIED; - reportPath(path, "Failed while reading QDMI device configuration", - [](const std::string_view pathText) { - qdmi::diagnostics::error( - "Failed while reading QDMI device configuration '{}'", - pathText); - }); + const auto source = qdmi::detail::pathToString(path); + auto buffer = llvm::MemoryBuffer::getFile(source); + if (!buffer) { + if (bundled) { + status = QDMI_ERROR_FATAL; + } else if (buffer.getError() == std::errc::no_such_file_or_directory || + buffer.getError() == std::errc::not_a_directory) { + status = QDMI_ERROR_NOTFOUND; + } else { + status = QDMI_ERROR_PERMISSIONDENIED; + } + qdmi::diagnostics::error("Cannot read QDMI device configuration '{}': {}", + source, buffer.getError().message()); return std::nullopt; } status = QDMI_SUCCESS; return LoadedDeviceConfiguration{ - .json = std::move(json), - .source = path.string(), + .json = (*buffer)->getBuffer().str(), + .source = source, }; } } // namespace diff --git a/src/qdmi/devices/sc/Configuration.cpp b/src/qdmi/devices/sc/Configuration.cpp index 5b9d54060d..a5e9c86357 100644 --- a/src/qdmi/devices/sc/Configuration.cpp +++ b/src/qdmi/devices/sc/Configuration.cpp @@ -21,9 +21,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -314,18 +311,4 @@ llvm::Expected readJSON(const std::string& path) { } return readJSON((*buffer)->getBuffer(), path); } -llvm::Expected readJSON(std::istream& stream, std::string_view source) { - try { - const std::string json{std::istreambuf_iterator(stream), - std::istreambuf_iterator()}; - if (stream.bad()) { - return llvm::createStringError(llvm::StringRef(source) + - ": failed to read JSON stream"); - } - return readJSON(json, source); - } catch (const std::ios_base::failure& error) { - return llvm::createStringError(llvm::StringRef(source) + ": " + - error.what()); - } -} } // namespace sc diff --git a/test/qdmi/devices/sc/test_configuration.cpp b/test/qdmi/devices/sc/test_configuration.cpp index 5d2859ce54..221d027588 100644 --- a/test/qdmi/devices/sc/test_configuration.cpp +++ b/test/qdmi/devices/sc/test_configuration.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "qdmi/TestUtils.hpp" #include "qdmi/devices/sc/Configuration.hpp" #include "gmock/gmock-matchers.h" @@ -21,12 +22,11 @@ #include #include #include +#include #include -#include -#include #include -#include #include +#include #include #include @@ -363,19 +363,16 @@ TEST(ScConfigurationTest, AcceptsExplicitHigherAritySites) { (std::vector{2, 0, 1})); } -TEST(ScConfigurationTest, ReportsStreamReadFailures) { - class FailingBuffer final : public std::streambuf { - protected: - int_type underflow() override { - throw std::ios_base::failure("injected read failure"); - } - } buffer; - std::istream stream(&buffer); - auto parsed = readJSON(stream, "failing-stream"); +TEST(ScConfigurationTest, ReportsFileReadFailures) { + const mqt::test::TemporaryDirectory directory; + const auto path = (directory.path() / "missing.json").string(); + auto parsed = readJSON(path); ASSERT_FALSE(static_cast(parsed)); - const auto message = llvm::toString(parsed.takeError()); - EXPECT_THAT(message, testing::HasSubstr("failing-stream")); - EXPECT_THAT(message, testing::HasSubstr("injected read failure")); + auto error = parsed.takeError(); + llvm::visitErrors(error, [](const llvm::ErrorInfoBase& info) { + EXPECT_EQ(info.convertToErrorCode(), std::errc::no_such_file_or_directory); + }); + EXPECT_THAT(llvm::toString(std::move(error)), testing::HasSubstr(path)); } } // namespace sc diff --git a/test/qdmi/devices/sc/test_device.cpp b/test/qdmi/devices/sc/test_device.cpp index 06be930252..84967361d4 100644 --- a/test/qdmi/devices/sc/test_device.cpp +++ b/test/qdmi/devices/sc/test_device.cpp @@ -455,26 +455,29 @@ TEST(ScRuntimeConfiguration, SelectsEnvironmentAndExplicitFileSources) { } TEST(ScRuntimeConfiguration, MapsMissingExplicitFileToNotFound) { - MQT_SC_QDMI_Device_Session session = nullptr; - ASSERT_EQ(MQT_SC_QDMI_device_session_alloc(&session), QDMI_SUCCESS); - constexpr auto missing = - std::to_array("missing-sc-device-configuration.json"); - ASSERT_EQ(MQT_SC_QDMI_device_session_set_parameter( - session, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2, missing.size(), - missing.data()), - QDMI_SUCCESS); - testing::internal::CaptureStderr(); - const auto status = MQT_SC_QDMI_device_session_init(session); - const auto diagnostic = testing::internal::GetCapturedStderr(); - EXPECT_EQ(status, QDMI_ERROR_NOTFOUND); - EXPECT_THAT( - diagnostic, - testing::AllOf( - testing::HasSubstr("[mqt-core] [error]"), - testing::HasSubstr("QDMI device configuration " - "'missing-sc-device-configuration.json' does not " - "exist"))); - MQT_SC_QDMI_device_session_free(session); + const mqt::test::TemporaryDirectory directory; + const auto regularFile = directory.write("regular-file", ""); + for (const auto& path : { + directory.path() / "missing.json", + regularFile / "configuration.json", + }) { + const auto missing = path.string(); + SCOPED_TRACE(missing); + MQT_SC_QDMI_Device_Session session = nullptr; + ASSERT_EQ(MQT_SC_QDMI_device_session_alloc(&session), QDMI_SUCCESS); + ASSERT_EQ(MQT_SC_QDMI_device_session_set_parameter( + session, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2, + missing.size() + 1, missing.c_str()), + QDMI_SUCCESS); + testing::internal::CaptureStderr(); + const auto status = MQT_SC_QDMI_device_session_init(session); + const auto diagnostic = testing::internal::GetCapturedStderr(); + EXPECT_EQ(status, QDMI_ERROR_NOTFOUND); + EXPECT_THAT(diagnostic, + testing::AllOf(testing::HasSubstr("[mqt-core] [error]"), + testing::HasSubstr(missing))); + MQT_SC_QDMI_device_session_free(session); + } } TEST(ScRuntimeConfiguration, MissingCalibrationReturnsNotSupported) {