mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
See: * https://github.com/llvm/llvm-project/pull/161045 * https://github.com/llvm/llvm-project/pull/160979
266 lines
7.7 KiB
C++
266 lines
7.7 KiB
C++
//===--- MockPlugin.cpp ---------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2023 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift-c/MockPlugin/MockPlugin.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/JSON.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
namespace {
|
|
struct TestItem {
|
|
llvm::json::Value expect;
|
|
llvm::json::Value response;
|
|
bool handled = false;
|
|
|
|
TestItem(const llvm::json::Value expect, const llvm::json::Value response)
|
|
: expect(expect), response(response) {}
|
|
};
|
|
|
|
struct TestRunner {
|
|
std::vector<TestItem> items;
|
|
|
|
TestRunner() {}
|
|
static std::unique_ptr<TestRunner> create(const char *testSpecStr);
|
|
|
|
llvm::json::Value createResponse(const TestItem &item,
|
|
const llvm::json::Value &req);
|
|
|
|
TestItem *findMatchItem(const llvm::json::Value &req);
|
|
|
|
int run();
|
|
};
|
|
} // namespace
|
|
|
|
std::unique_ptr<TestRunner> TestRunner::create(const char *testSpecStr) {
|
|
// Read test spec.
|
|
auto testSpec = llvm::json::parse(testSpecStr);
|
|
if (!testSpec) {
|
|
llvm::errs() << "failed to parse test spec JSON\n";
|
|
llvm::handleAllErrors(testSpec.takeError(),
|
|
[](const llvm::ErrorInfoBase &err) {
|
|
llvm::errs() << err.message() << "\n";
|
|
});
|
|
return nullptr;
|
|
}
|
|
|
|
auto *items = testSpec->getAsArray();
|
|
if (!items) {
|
|
llvm::errs() << "test spec list must be an array\n";
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<TestRunner> runner(new TestRunner());
|
|
|
|
for (auto item : *items) {
|
|
auto *obj = item.getAsObject();
|
|
if (!obj) {
|
|
llvm::errs() << "test spec must be an object\n";
|
|
return nullptr;
|
|
}
|
|
auto *expect = obj->get("expect");
|
|
auto *response = obj->get("response");
|
|
if (!expect || !response) {
|
|
llvm::errs() << "test item must have 'expect' and 'response'\n";
|
|
return nullptr;
|
|
}
|
|
runner->items.emplace_back(*expect, *response);
|
|
}
|
|
|
|
return runner;
|
|
}
|
|
|
|
/// Replace string values starting with '=req' (e.g. '"=req.foo.bar[2].baz"') in
|
|
/// \p value with the corresponding values from \p req .
|
|
static const llvm::json::Value substitute(const llvm::json::Value &value,
|
|
const llvm::json::Value &req) {
|
|
|
|
// Recursively substitute objects and arrays.
|
|
if (auto *obj = value.getAsObject()) {
|
|
llvm::json::Object copy;
|
|
for (auto &kv : *obj) {
|
|
copy[kv.first] = substitute(kv.second, req);
|
|
}
|
|
return copy;
|
|
}
|
|
if (auto *arr = value.getAsArray()) {
|
|
llvm::json::Array copy;
|
|
for (auto &elem : *arr) {
|
|
copy.push_back(substitute(elem, req));
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
auto str = value.getAsString();
|
|
if (!str || !str->starts_with("=req")) {
|
|
// Not a substitution.
|
|
return value;
|
|
}
|
|
|
|
auto path = str->substr(4);
|
|
const llvm::json::Value *subst = &req;
|
|
while (!path.empty()) {
|
|
// '.' <alphanum> -> object key.
|
|
if (path.starts_with(".")) {
|
|
if (subst->kind() != llvm::json::Value::Object)
|
|
return "<substitution error: not an object>";
|
|
path = path.substr(1);
|
|
auto keyLength =
|
|
path.find_if([](char c) { return !llvm::isAlnum(c) && c != '_'; });
|
|
auto key = path.slice(0, keyLength);
|
|
subst = subst->getAsObject()->get(key);
|
|
path = path.substr(keyLength);
|
|
continue;
|
|
}
|
|
// '[' <digit>+ ']' -> array index.
|
|
if (path.starts_with("[")) {
|
|
if (subst->kind() != llvm::json::Value::Array)
|
|
return "<substitution error: not an array>";
|
|
path = path.substr(1);
|
|
auto idxlength = path.find_if([](char c) { return !llvm::isDigit(c); });
|
|
size_t idx;
|
|
(void)path.slice(0, idxlength).getAsInteger(10, idx);
|
|
subst = &(*subst->getAsArray())[idx];
|
|
path = path.substr(idxlength);
|
|
if (!path.starts_with("]"))
|
|
return "<substitution error: missing ']' after digits>";
|
|
path = path.substr(1);
|
|
continue;
|
|
}
|
|
// Malformed.
|
|
return "<substitution error: malformed path>";
|
|
}
|
|
return *subst;
|
|
}
|
|
|
|
llvm::json::Value TestRunner::createResponse(const TestItem &item,
|
|
const llvm::json::Value &req) {
|
|
auto response = item.response;
|
|
return substitute(response, req);
|
|
}
|
|
|
|
/// Check if \p expect matches \p val .
|
|
static bool match(const llvm::json::Value &expect,
|
|
const llvm::json::Value &val) {
|
|
if (expect.kind() != val.kind())
|
|
return false;
|
|
|
|
switch (expect.kind()) {
|
|
case llvm::json::Value::Object: {
|
|
auto *exp = expect.getAsObject();
|
|
auto *obj = val.getAsObject();
|
|
return llvm::all_of(*exp, [obj](const auto &kv) {
|
|
auto *val = obj->get(kv.first.str());
|
|
return val && match(kv.second, *val);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
case llvm::json::Value::Array: {
|
|
auto *exp = expect.getAsArray();
|
|
auto *arr = val.getAsArray();
|
|
if (exp->size() != arr->size())
|
|
return false;
|
|
return llvm::all_of_zip(
|
|
*exp, *arr,
|
|
[](const auto &expect, const auto &val) { return match(expect, val); });
|
|
}
|
|
|
|
default:
|
|
return expect == val;
|
|
}
|
|
}
|
|
|
|
TestItem *TestRunner::findMatchItem(const llvm::json::Value &req) {
|
|
for (auto &item : items) {
|
|
if (match(item.expect, req)) {
|
|
return &item;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int TestRunner::run() {
|
|
size_t ioSize;
|
|
while (true) {
|
|
// Read request header.
|
|
uint64_t request_header;
|
|
ioSize = fread(&request_header, sizeof(request_header), 1, stdin);
|
|
if (!ioSize) {
|
|
// STDIN is closed.
|
|
return 0;
|
|
}
|
|
|
|
// Read request data.
|
|
auto request_size = llvm::support::endian::byte_swap(
|
|
request_header, llvm::endianness::little);
|
|
llvm::SmallVector<char, 0> request_data;
|
|
request_data.assign(request_size, 0);
|
|
ioSize = fread(request_data.data(), request_size, 1, stdin);
|
|
if (!ioSize) {
|
|
llvm::errs() << "failed to read request data\n";
|
|
return 1;
|
|
}
|
|
|
|
// Parse request object.
|
|
auto request_obj =
|
|
llvm::json::parse({request_data.data(), request_data.size()});
|
|
if (!request_obj) {
|
|
llvm::handleAllErrors(
|
|
request_obj.takeError(),
|
|
[](llvm::ErrorInfoBase &err) { llvm::errs() << err.message(); });
|
|
return 1;
|
|
}
|
|
|
|
// Handle
|
|
auto *item = findMatchItem(*request_obj);
|
|
if (!item) {
|
|
llvm::errs() << "couldn't find matching item for request: " << *request_obj
|
|
<< "\n";
|
|
return 1;
|
|
}
|
|
if (item->handled) {
|
|
llvm::errs() << "request is already handled\n";
|
|
return 1;
|
|
}
|
|
item->handled = true;
|
|
auto response = createResponse(*item, *request_obj);
|
|
|
|
// Write response data.
|
|
llvm::SmallVector<char, 0> response_data;
|
|
llvm::raw_svector_ostream(response_data) << response;
|
|
auto response_size = response_data.size();
|
|
auto response_header = llvm::support::endian::byte_swap<uint64_t>(
|
|
response_size, llvm::endianness::little);
|
|
ioSize = fwrite(&response_header, sizeof(response_header), 1, stdout);
|
|
if (!ioSize) {
|
|
llvm::errs() << "failed to write response header\n";
|
|
return 1;
|
|
}
|
|
ioSize = fwrite(response_data.data(), response_size, 1, stdout);
|
|
if (!ioSize) {
|
|
llvm::errs() << "failed to write response data\n";
|
|
return 1;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
int _mock_plugin_main(const char *testSpectStr) {
|
|
auto runner = TestRunner::create(testSpectStr);
|
|
if (!runner) {
|
|
return 1;
|
|
}
|
|
return runner->run();
|
|
}
|