Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions include/mqt-core/qdmi/devices/sc/Configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#include "llvm/Support/Error.h"

#include <cstdint>
#include <istream>
#include <optional>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -75,8 +74,7 @@ struct Device {
/// Parse and validate a device description and report errors with @p source.
[[nodiscard]] llvm::Expected<Device> readJSON(std::string_view json,
std::string_view source);
[[nodiscard]] llvm::Expected<Device>
readJSON(std::istream& stream, std::string_view source = "input");
/// Read a JSON file and return validation or file-access errors.
[[nodiscard]] llvm::Expected<Device> readJSON(const std::string& path);

} // namespace sc
87 changes: 17 additions & 70 deletions src/qdmi/common/DeviceConfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cerrno>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <format>
#include <fstream>
#include <ios>
#include <iterator>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>

#ifdef _WIN32
#include <memory>
Expand Down Expand Up @@ -103,77 +100,27 @@ namespace qdmi::detail {
}

namespace {
template <class Reporter>
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>(reporter)(std::string_view(pathText));
#else
std::forward<Reporter>(reporter)(std::string_view(path.native()));
#endif
} catch (...) {
qdmi::diagnostics::error(fallback);
}
}

[[nodiscard]] std::optional<LoadedDeviceConfiguration>
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<char>(input),
std::istreambuf_iterator<char>()};
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
Expand Down
17 changes: 0 additions & 17 deletions src/qdmi/devices/sc/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <ios>
#include <istream>
#include <iterator>
#include <limits>
#include <optional>
#include <set>
Expand Down Expand Up @@ -314,18 +311,4 @@ llvm::Expected<Device> readJSON(const std::string& path) {
}
return readJSON((*buffer)->getBuffer(), path);
}
llvm::Expected<Device> readJSON(std::istream& stream, std::string_view source) {
try {
const std::string json{std::istreambuf_iterator<char>(stream),
std::istreambuf_iterator<char>()};
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
27 changes: 12 additions & 15 deletions test/qdmi/devices/sc/test_configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Licensed under the MIT License
*/

#include "qdmi/TestUtils.hpp"
#include "qdmi/devices/sc/Configuration.hpp"

#include "gmock/gmock-matchers.h"
Expand All @@ -21,12 +22,11 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <ios>
#include <istream>
#include <limits>
#include <streambuf>
#include <string>
#include <system_error>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -363,19 +363,16 @@ TEST(ScConfigurationTest, AcceptsExplicitHigherAritySites) {
(std::vector<uint64_t>{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<bool>(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
43 changes: 23 additions & 20 deletions test/qdmi/devices/sc/test_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading