| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| //===--- XPCTransport.cpp - sending and receiving LSP messages over XPC ---===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| #include "Conversion.h" | ||
| #include "Logger.h" | ||
| #include "Protocol.h" // For LSPError | ||
| #include "Transport.h" | ||
| #include "llvm/Support/Errno.h" | ||
|
|
||
| #include <xpc/xpc.h> | ||
|
|
||
| using namespace llvm; | ||
| using namespace clang; | ||
| using namespace clangd; | ||
|
|
||
| namespace { | ||
|
|
||
| json::Object encodeError(Error E) { | ||
| std::string Message; | ||
| ErrorCode Code = ErrorCode::UnknownErrorCode; | ||
| if (Error Unhandled = | ||
| handleErrors(std::move(E), [&](const LSPError &L) -> Error { | ||
| Message = L.Message; | ||
| Code = L.Code; | ||
| return Error::success(); | ||
| })) | ||
| Message = toString(std::move(Unhandled)); | ||
|
|
||
| return json::Object{ | ||
| {"message", std::move(Message)}, | ||
| {"code", int64_t(Code)}, | ||
| }; | ||
| } | ||
|
|
||
| Error decodeError(const json::Object &O) { | ||
| std::string Msg = O.getString("message").getValueOr("Unspecified error"); | ||
| if (auto Code = O.getInteger("code")) | ||
| return make_error<LSPError>(std::move(Msg), ErrorCode(*Code)); | ||
| return make_error<StringError>(std::move(Msg), inconvertibleErrorCode()); | ||
| } | ||
|
|
||
| // C "closure" for XPCTransport::loop() method | ||
| namespace xpcClosure { | ||
| void connection_handler(xpc_connection_t clientConnection); | ||
| } | ||
|
|
||
| class XPCTransport : public Transport { | ||
| public: | ||
| XPCTransport() {} | ||
|
|
||
| void notify(StringRef Method, json::Value Params) override { | ||
| sendMessage(json::Object{ | ||
| {"jsonrpc", "2.0"}, | ||
| {"method", Method}, | ||
| {"params", std::move(Params)}, | ||
| }); | ||
| } | ||
| void call(StringRef Method, json::Value Params, json::Value ID) override { | ||
| sendMessage(json::Object{ | ||
| {"jsonrpc", "2.0"}, | ||
| {"id", std::move(ID)}, | ||
| {"method", Method}, | ||
| {"params", std::move(Params)}, | ||
| }); | ||
| } | ||
| void reply(json::Value ID, Expected<json::Value> Result) override { | ||
| if (Result) { | ||
| sendMessage(json::Object{ | ||
| {"jsonrpc", "2.0"}, | ||
| {"id", std::move(ID)}, | ||
| {"result", std::move(*Result)}, | ||
| }); | ||
| } else { | ||
| sendMessage(json::Object{ | ||
| {"jsonrpc", "2.0"}, | ||
| {"id", std::move(ID)}, | ||
| {"error", encodeError(Result.takeError())}, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| Error loop(MessageHandler &Handler) override; | ||
|
|
||
| private: | ||
| // Needs access to handleMessage() and resetClientConnection() | ||
| friend void xpcClosure::connection_handler(xpc_connection_t clientConnection); | ||
|
|
||
| // Dispatches incoming message to Handler onNotify/onCall/onReply. | ||
| bool handleMessage(json::Value Message, MessageHandler &Handler); | ||
| void sendMessage(json::Value Message) { | ||
| xpc_object_t response = jsonToXpc(Message); | ||
| xpc_connection_send_message(clientConnection, response); | ||
| xpc_release(response); | ||
| } | ||
| void resetClientConnection(xpc_connection_t newClientConnection) { | ||
| clientConnection = newClientConnection; | ||
| } | ||
| xpc_connection_t clientConnection; | ||
| }; | ||
|
|
||
| bool XPCTransport::handleMessage(json::Value Message, MessageHandler &Handler) { | ||
| // Message must be an object with "jsonrpc":"2.0". | ||
| auto *Object = Message.getAsObject(); | ||
| if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) { | ||
| elog("Not a JSON-RPC 2.0 message: {0:2}", Message); | ||
| return false; | ||
| } | ||
| // ID may be any JSON value. If absent, this is a notification. | ||
| Optional<json::Value> ID; | ||
| if (auto *I = Object->get("id")) | ||
| ID = std::move(*I); | ||
| auto Method = Object->getString("method"); | ||
| if (!Method) { // This is a response. | ||
| if (!ID) { | ||
| elog("No method and no response ID: {0:2}", Message); | ||
| return false; | ||
| } | ||
| if (auto *Err = Object->getObject("error")) | ||
| return Handler.onReply(std::move(*ID), decodeError(*Err)); | ||
| // Result should be given, use null if not. | ||
| json::Value Result = nullptr; | ||
| if (auto *R = Object->get("result")) | ||
| Result = std::move(*R); | ||
| return Handler.onReply(std::move(*ID), std::move(Result)); | ||
| } | ||
| // Params should be given, use null if not. | ||
| json::Value Params = nullptr; | ||
| if (auto *P = Object->get("params")) | ||
| Params = std::move(*P); | ||
|
|
||
| if (ID) | ||
| return Handler.onCall(*Method, std::move(Params), std::move(*ID)); | ||
| else | ||
| return Handler.onNotify(*Method, std::move(Params)); | ||
| } | ||
|
|
||
| namespace xpcClosure { | ||
| // "owner" of this "closure object" - necessary for propagating connection to | ||
| // XPCTransport so it can send messages to the client. | ||
| XPCTransport *TransportObject = nullptr; | ||
| Transport::MessageHandler *HandlerPtr = nullptr; | ||
|
|
||
| void connection_handler(xpc_connection_t clientConnection) { | ||
| xpc_connection_set_target_queue(clientConnection, dispatch_get_main_queue()); | ||
|
|
||
| xpc_transaction_begin(); | ||
|
|
||
| TransportObject->resetClientConnection(clientConnection); | ||
|
|
||
| xpc_connection_set_event_handler(clientConnection, ^(xpc_object_t message) { | ||
| if (message == XPC_ERROR_CONNECTION_INVALID) { | ||
| // connection is being terminated | ||
| log("Received XPC_ERROR_CONNECTION_INVALID message - returning from the " | ||
| "event_handler."); | ||
| return; | ||
| } | ||
|
|
||
| if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) { | ||
| log("Received XPC message of unknown type - returning from the " | ||
| "event_handler."); | ||
| return; | ||
| } | ||
|
|
||
| const json::Value Doc = xpcToJson(message); | ||
| if (Doc == json::Value(nullptr)) { | ||
| log("XPC message was converted to Null JSON message - returning from the " | ||
| "event_handler."); | ||
| return; | ||
| } | ||
|
|
||
| vlog("<<< {0}\n", Doc); | ||
|
|
||
| if (!TransportObject->handleMessage(std::move(Doc), *HandlerPtr)) { | ||
| log("Received exit notification - cancelling connection."); | ||
| xpc_connection_cancel(xpc_dictionary_get_remote_connection(message)); | ||
| xpc_transaction_end(); | ||
| } | ||
| }); | ||
|
|
||
| xpc_connection_resume(clientConnection); | ||
| } | ||
| } // namespace xpcClosure | ||
|
|
||
| Error XPCTransport::loop(MessageHandler &Handler) { | ||
| assert(xpcClosure::TransportObject == nullptr && | ||
| "TransportObject has already been set."); | ||
| // This looks scary since lifetime of this (or any) XPCTransport object has | ||
| // to fully contain lifetime of any XPC connection. In practise any Transport | ||
| // object is destroyed only at the end of main() which is always after | ||
| // exit of xpc_main(). | ||
| xpcClosure::TransportObject = this; | ||
|
|
||
| assert(xpcClosure::HandlerPtr == nullptr && | ||
| "HandlerPtr has already been set."); | ||
| xpcClosure::HandlerPtr = &Handler; | ||
|
|
||
| xpc_main(xpcClosure::connection_handler); | ||
| // xpc_main doesn't ever return | ||
| return errorCodeToError(std::make_error_code(std::errc::io_error)); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| namespace clang { | ||
| namespace clangd { | ||
|
|
||
| std::unique_ptr<Transport> newXPCTransport() { | ||
| return llvm::make_unique<XPCTransport>(); | ||
| } | ||
|
|
||
| } // namespace clangd | ||
| } // namespace clang |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>CFBundleDevelopmentRegion</key> | ||
| <string>English</string> | ||
| <key>CFBundleExecutable</key> | ||
| <string>${CLANGD_XPC_FRAMEWORK_NAME}</string> | ||
| <key>CFBundleIconFile</key> | ||
| <string></string> | ||
| <key>CFBundleIdentifier</key> | ||
| <string>org.llvm.${CLANGD_XPC_FRAMEWORK_NAME}</string> | ||
| <key>CFBundleInfoDictionaryVersion</key> | ||
| <string>6.0</string> | ||
| <key>CFBundleName</key> | ||
| <string>${CLANGD_XPC_FRAMEWORK_NAME}</string> | ||
| <key>CFBundlePackageType</key> | ||
| <string>FMWK</string> | ||
| <key>CFBundleSignature</key> | ||
| <string>????</string> | ||
| <key>CFBundleVersion</key> | ||
| <string></string> | ||
| <key>CFBundleShortVersionString</key> | ||
| <string>1.0</string> | ||
| <key>CSResourcesFileMapped</key> | ||
| <true/> | ||
| </dict> | ||
| </plist> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>CFBundleExecutable</key> | ||
| <string>${CLANGD_XPC_SERVICE_NAME}</string> | ||
| <key>CFBundleIdentifier</key> | ||
| <string>${CLANGD_XPC_SERVICE_BUNDLE_NAME}</string> | ||
| <key>CFBundleInfoDictionaryVersion</key> | ||
| <string>6.0</string> | ||
| <key>CFBundleName</key> | ||
| <string>${CLANGD_XPC_SERVICE_NAME}</string> | ||
| <key>CFBundlePackageType</key> | ||
| <string>XPC!</string> | ||
| <key>CFBundleVersion</key> | ||
| <string></string> | ||
| <key>CFBundleShortVersionString</key> | ||
| <string>1.0</string> | ||
| <key>XPCService</key> | ||
| <dict> | ||
| <key>ServiceType</key> | ||
| <string>Application</string> | ||
| <key>EnvironmentVariables</key> | ||
| <dict> | ||
| <key>CLANGD_AS_XPC_SERVICE</key> | ||
| <string>1</string> | ||
| </dict> | ||
| </dict> | ||
| </dict> | ||
| </plist> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # Creates the ClangdXPC framework. | ||
| macro(create_clangd_xpc_framework target name) | ||
| set(CLANGD_FRAMEWORK_LOCATION "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${name}.framework") | ||
| set(CLANGD_FRAMEWORK_OUT_LOCATION "${CLANGD_FRAMEWORK_LOCATION}/Versions/A") | ||
|
|
||
| # Create the framework info PLIST. | ||
| set(CLANGD_XPC_FRAMEWORK_NAME "${name}") | ||
| configure_file( | ||
| "${CLANGD_XPC_SOURCE_DIR}/cmake/Info.plist.in" | ||
| "${CLANGD_XPC_BINARY_DIR}/${name}.Info.plist") | ||
|
|
||
| set(CLANGD_XPC_SERVICE_NAME "clangd") | ||
| set(CLANGD_XPC_SERVICE_OUT_LOCATION | ||
| "${CLANGD_FRAMEWORK_OUT_LOCATION}/XPCServices/${CLANGD_XPC_SERVICE_NAME}.xpc/Contents") | ||
|
|
||
| # Create the XPC service info PLIST. | ||
| set(CLANGD_XPC_SERVICE_BUNDLE_NAME "org.llvm.${CLANGD_XPC_SERVICE_NAME}") | ||
| configure_file( | ||
| "${CLANGD_XPC_SOURCE_DIR}/cmake/XPCServiceInfo.plist.in" | ||
| "${CLANGD_XPC_BINARY_DIR}/${name}Service.Info.plist") | ||
|
|
||
| # Create the custom command | ||
| add_custom_command(OUTPUT ${CLANGD_FRAMEWORK_LOCATION} | ||
| # Copy the PLIST. | ||
| COMMAND ${CMAKE_COMMAND} -E copy | ||
| "${CLANGD_XPC_BINARY_DIR}/${name}.Info.plist" | ||
| "${CLANGD_FRAMEWORK_OUT_LOCATION}/Resources/Info.plist" | ||
|
|
||
| # Copy the framework binary. | ||
| COMMAND ${CMAKE_COMMAND} -E copy | ||
| "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${target}.dylib" | ||
| "${CLANGD_FRAMEWORK_OUT_LOCATION}/${name}" | ||
|
|
||
| # Copy the XPC Service PLIST. | ||
| COMMAND ${CMAKE_COMMAND} -E copy | ||
| "${CLANGD_XPC_BINARY_DIR}/${name}Service.Info.plist" | ||
| "${CLANGD_XPC_SERVICE_OUT_LOCATION}/Info.plist" | ||
|
|
||
| # Copy the Clangd binary. | ||
| COMMAND ${CMAKE_COMMAND} -E copy | ||
| "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/clangd" | ||
| "${CLANGD_XPC_SERVICE_OUT_LOCATION}/MacOS/clangd" | ||
|
|
||
| COMMAND ${CMAKE_COMMAND} -E create_symlink "A" | ||
| "${CLANGD_FRAMEWORK_LOCATION}/Versions/Current" | ||
|
|
||
| COMMAND ${CMAKE_COMMAND} -E create_symlink | ||
| "Versions/Current/Resources" | ||
| "${CLANGD_FRAMEWORK_LOCATION}/Resources" | ||
|
|
||
| COMMAND ${CMAKE_COMMAND} -E create_symlink | ||
| "Versions/Current/XPCServices" | ||
| "${CLANGD_FRAMEWORK_LOCATION}/XPCServices" | ||
|
|
||
| COMMAND ${CMAKE_COMMAND} -E create_symlink | ||
| "Versions/Current/${name}" | ||
| "${CLANGD_FRAMEWORK_LOCATION}/${name}" | ||
|
|
||
| DEPENDS | ||
| "${CLANGD_XPC_BINARY_DIR}/${name}.Info.plist" | ||
| "${CLANGD_XPC_BINARY_DIR}/${name}Service.Info.plist" | ||
| clangd | ||
| COMMENT "Creating ClangdXPC framework" | ||
| VERBATIM | ||
| ) | ||
|
|
||
| add_custom_target( | ||
| ClangdXPC | ||
| DEPENDS | ||
| ${target} | ||
| ${CLANGD_FRAMEWORK_LOCATION} | ||
| ) | ||
| endmacro(create_clangd_xpc_framework) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
|
|
||
| set(SOURCES | ||
| ClangdXPC.cpp) | ||
| add_clang_library(ClangdXPCLib SHARED | ||
| ${SOURCES} | ||
| DEPENDS | ||
| clangd | ||
| ) | ||
| create_clangd_xpc_framework(ClangdXPCLib "ClangdXPC") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
|
|
||
| /// Returns the bundle identifier of the Clangd XPC service. | ||
| extern "C" const char *clangd_xpc_get_bundle_identifier() { | ||
| return "org.llvm.clangd"; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| include_directories( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/../../ | ||
| ) | ||
|
|
||
| add_clang_tool( | ||
| clangd-xpc-test-client | ||
| ClangdXPCTestClient.cpp | ||
|
|
||
| DEPENDS ClangdXPC | ||
| ) | ||
|
|
||
| set(LLVM_LINK_COMPONENTS | ||
| support | ||
| ) | ||
|
|
||
| target_link_libraries(clangd-xpc-test-client | ||
| PRIVATE | ||
| clangBasic | ||
| clangDaemon | ||
| clangFormat | ||
| clangFrontend | ||
| clangSema | ||
| clangTooling | ||
| clangToolingCore | ||
| clangdXpcJsonConversions | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| #include "xpc/Conversion.h" | ||
| #include "clang/Basic/LLVM.h" | ||
| #include "llvm/ADT/SmallString.h" | ||
| #include "llvm/Support/LineIterator.h" | ||
| #include "llvm/Support/MemoryBuffer.h" | ||
| #include "llvm/Support/Path.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
| #include <dlfcn.h> | ||
| #include <stdio.h> | ||
| #include <string> | ||
| #include <xpc/xpc.h> | ||
|
|
||
| typedef const char *(*clangd_xpc_get_bundle_identifier_t)(void); | ||
|
|
||
| using namespace llvm; | ||
| using namespace clang; | ||
|
|
||
| std::string getLibraryPath() { | ||
| Dl_info info; | ||
| if (dladdr((void *)(uintptr_t)getLibraryPath, &info) == 0) | ||
| llvm_unreachable("Call to dladdr() failed"); | ||
| llvm::SmallString<128> LibClangPath; | ||
| LibClangPath = llvm::sys::path::parent_path( | ||
| llvm::sys::path::parent_path(info.dli_fname)); | ||
| llvm::sys::path::append(LibClangPath, "lib", "ClangdXPC.framework", | ||
| "ClangdXPC"); | ||
| return LibClangPath.str(); | ||
| } | ||
|
|
||
| static void dumpXPCObject(xpc_object_t Object, llvm::raw_ostream &OS) { | ||
| xpc_type_t Type = xpc_get_type(Object); | ||
| if (Type == XPC_TYPE_DICTIONARY) { | ||
| json::Value Json = clang::clangd::xpcToJson(Object); | ||
| OS << Json; | ||
| } else { | ||
| OS << "<UNKNOWN>"; | ||
| } | ||
| } | ||
|
|
||
| int main(int argc, char *argv[]) { | ||
| // Open the ClangdXPC dylib in the framework. | ||
| std::string LibPath = getLibraryPath(); | ||
| void *dlHandle = dlopen(LibPath.c_str(), RTLD_LOCAL | RTLD_FIRST); | ||
| if (!dlHandle) | ||
| return 1; | ||
|
|
||
| // Lookup the XPC service bundle name, and launch it. | ||
| clangd_xpc_get_bundle_identifier_t clangd_xpc_get_bundle_identifier = | ||
| (clangd_xpc_get_bundle_identifier_t)dlsym( | ||
| dlHandle, "clangd_xpc_get_bundle_identifier"); | ||
| xpc_connection_t conn = xpc_connection_create( | ||
| clangd_xpc_get_bundle_identifier(), dispatch_get_main_queue()); | ||
|
|
||
| // Dump the XPC events. | ||
| xpc_connection_set_event_handler(conn, ^(xpc_object_t event) { | ||
| if (event == XPC_ERROR_CONNECTION_INVALID) { | ||
| llvm::errs() << "Received XPC_ERROR_CONNECTION_INVALID."; | ||
| exit(EXIT_SUCCESS); | ||
| } | ||
| if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { | ||
| llvm::errs() << "Received XPC_ERROR_CONNECTION_INTERRUPTED."; | ||
| exit(EXIT_SUCCESS); | ||
| } | ||
|
|
||
| dumpXPCObject(event, llvm::outs()); | ||
| llvm::outs() << "\n"; | ||
| }); | ||
|
|
||
| xpc_connection_resume(conn); | ||
|
|
||
| // Read the input to determine the things to send to clangd. | ||
| llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Stdin = | ||
| llvm::MemoryBuffer::getSTDIN(); | ||
| if (!Stdin) { | ||
| llvm::errs() << "Failed to get STDIN!\n"; | ||
| return 1; | ||
| } | ||
| for (llvm::line_iterator It(**Stdin, /*SkipBlanks=*/true, | ||
| /*CommentMarker=*/'#'); | ||
| !It.is_at_eof(); ++It) { | ||
| StringRef Line = *It; | ||
| if (auto Request = json::parse(Line)) { | ||
| xpc_object_t Object = clangd::jsonToXpc(*Request); | ||
| xpc_connection_send_message(conn, Object); | ||
| } else { | ||
| llvm::errs() << llvm::Twine("JSON parse error: ") | ||
| << llvm::toString(Request.takeError()); | ||
| return 1; | ||
| } | ||
| } | ||
|
|
||
| dispatch_main(); | ||
|
|
||
| // dispatch_main() doesn't return | ||
| return EXIT_FAILURE; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # RUN: clangd-xpc-test-client < %s | FileCheck %s | ||
| # REQUIRES: clangd-xpc-support | ||
|
|
||
| {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"test:///workspace","capabilities":{},"trace":"off"}} | ||
| # CHECK: {"id":0,"jsonrpc":"2.0","result":{"capabilities" | ||
|
|
||
| {"jsonrpc":"2.0","id":3,"method":"shutdown"} | ||
| # CHECK: {"id":3,"jsonrpc":"2.0","result":null} | ||
|
|
||
| {"jsonrpc":"2.0","method":"exit"} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| set(LLVM_LINK_COMPONENTS | ||
| support | ||
| ) | ||
|
|
||
| get_filename_component(CLANGD_SOURCE_DIR | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/../../clangd REALPATH) | ||
| include_directories( | ||
| ${CLANGD_SOURCE_DIR} | ||
| ) | ||
|
|
||
| add_extra_unittest(ClangdXpcTests | ||
| ConversionTests.cpp | ||
| ) | ||
|
|
||
| target_link_libraries(ClangdXpcTests | ||
| PRIVATE | ||
| clangdXpcJsonConversions | ||
| clangDaemon | ||
| LLVMSupport | ||
| LLVMTestingSupport | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| //===-- ConversionTests.cpp --------------------------*- C++ -*-----------===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "xpc/Conversion.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| #include <limits> | ||
|
|
||
| namespace clang { | ||
| namespace clangd { | ||
| namespace { | ||
|
|
||
| using namespace llvm; | ||
|
|
||
| TEST(JsonXpcConversionTest, JsonToXpcToJson) { | ||
|
|
||
| for (auto &testcase : | ||
| {json::Value(false), json::Value(3.14), json::Value(42), | ||
| json::Value(-100), json::Value("foo"), json::Value(""), | ||
| json::Value("123"), json::Value(" "), | ||
| json::Value{true, "foo", nullptr, 42}, | ||
| json::Value(json::Object{ | ||
| {"a", true}, {"b", "foo"}, {"c", nullptr}, {"d", 42}})}) { | ||
| EXPECT_TRUE(testcase == xpcToJson(jsonToXpc(testcase))) << testcase; | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace clangd | ||
| } // namespace clang |