diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-11-23 16:23:15 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-23 16:23:15 -0500 |
| commit | 9e084ffab37c276d40931a58633041a2e10de623 (patch) | |
| tree | d85aa490968cdd3fe4bbcf305b593c6b86587685 /source | |
| parent | fd46034bf2de59b8ad51743e62b26359678432f7 (diff) | |
JSON-RPC test server (#2026)
* #include an absolute path didn't work - because paths were taken to always be relative.
* Use 'Process' to communicate with an command line tool.
* Remove slang-win-stream
* Tidy up windows ProcessUtil.
* First version of BufferedReadStream.
* Windows working IPC for steams.
* Test proxy count option.
* Split Process/ProcessUtil. Process is platform dependant. ProcessUtil are functions that are platform independent.
* First implementation of Unix Process interface.
* Unix process compiles on cygwin.
* Fix typo in unix process.
* Separate unix pipe stream error of invalid access, from pipe availability.
* Fix in standard line extraction.
* Make fd non blocking.
* Fix issues with Windows Process streams.
* Added UnixPipe.
* Some fixes around UnixPipeStream.
* Make a unix stream closed explicit.
* Hack to debug linux process/stream.
* Revert to old linux pipe handling.
* Pass executable path for unit tests.
Split out CommandLine into own source.
* Small improvements in process/command line.
* Check process behavior with crash.
* Make stderr and stdout unbuffered for crash testing.
* Only turn disable buffering in crash test.
* Disable crash test on CI.
* Fix crash on clang/linux.
* Enable crash test.
Remove _appendBuffer as can use StreamUtil functionality.
* Added inital processing for http headers.
* Small improvements to HttpHeader.
* First pass HTTPPacketConnection working on windows.
* Enable other Process communication tests.
* Update comments.
* WIP JSON RPC.
* Add terminate to Process.
Made JSONRPC a Util.
* Small tidy up around HTTPPacketConnection.
* Improve process termination options.
* WIP for test-server.
* Add diagnostics error handling to test-server.
* Improved JSON support.
Parsing/creating JSON-RPC messages.
* WIP JSONRPC parsing.
* First pass RttiInfo support.
* WIP converting between JSON/native types.
* Project files.
* Split out RttiUtil.
Made RttiInfo constuction thread safe.
* WIP RTTI<->JSON.
* Add diagnostics to JSON<->native conversions.
* Make RttiInfo for structs globals. Avoids problem around derived types (like pointers), being able to cause an abort.
* Add pointer support to RTTI.
Fixed some compilation issues on linux.
* Add fixed array support.
* Added Rtti unit test.
* Add rtti unit test.
* Split out quoted/unquoted key handling.
Fix bugs in JSON value/container.
Added JSON native test.
* Make default array allocator use malloc/free.
Remove the new[] handler (doesn't work on visuals studio).
* Fix for linux warning.
* Remove some test code.
* Fix issues on x86 win.
* Fix warning on aarch64.
* Fix some bugs in JSON parsing/handling.
Make Rtti work copy/dtor/ctor struct types.
* Testing JSON<->native with fixed array.
Make makeArrayView explicit if it's just a single value.
Added array type.
* Fix getting arrayView.
* Improve JSON diagnostic name.
* First pass refactor using Rtti for JSON RPC.
* First pass of test server using RTTI/JSON-RPC.
* Added JSONRPCConnection.
* Fix some naming issues.
* First pass of test-server working.
* Added unit test support for JSON-RPC test server.
* Fix compilation issues on linux around template handling.
* Typo fix.
* Fix a bug around SourceLoc lookup with JSONContainer.
* Set the console type to console for ISlangWriters.
* Small improvements to test-server.
* Small improvements in test-server.
* Small fix.
Diffstat (limited to 'source')
| -rw-r--r-- | source/compiler-core/slang-json-diagnostic-defs.h | 7 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-native.h | 5 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-rpc-connection.cpp | 286 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-rpc-connection.h | 161 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-rpc.cpp | 404 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-rpc.h | 151 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-value.cpp | 2 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-value.h | 3 | ||||
| -rw-r--r-- | source/compiler-core/slang-test-server-protocol.cpp | 43 | ||||
| -rw-r--r-- | source/compiler-core/slang-test-server-protocol.h | 51 | ||||
| -rw-r--r-- | source/core/slang-http.cpp | 24 | ||||
| -rw-r--r-- | source/core/slang-http.h | 5 | ||||
| -rw-r--r-- | source/core/slang-writer.h | 1 |
13 files changed, 727 insertions, 416 deletions
diff --git a/source/compiler-core/slang-json-diagnostic-defs.h b/source/compiler-core/slang-json-diagnostic-defs.h index 4733a3291..576f14beb 100644 --- a/source/compiler-core/slang-json-diagnostic-defs.h +++ b/source/compiler-core/slang-json-diagnostic-defs.h @@ -42,4 +42,11 @@ DIAGNOSTIC(20011, Error, fieldNotDefinedOnType, "field '$0' not defined on type DIAGNOSTIC(20011, Error, fieldRequiredOnType, "field '$0' required on '$1'") DIAGNOSTIC(20012, Error, tooManyElementsForArray, "too many elements ($0) for array array. Max allowed is $1") +// +// 3xxxx JSON-RPC +// + +DIAGNOSTIC(30000, Error, argsAreInvalid, "Args for '%0' are invalid") + + #undef DIAGNOSTIC diff --git a/source/compiler-core/slang-json-native.h b/source/compiler-core/slang-json-native.h index 66f20aafb..f9ec93119 100644 --- a/source/compiler-core/slang-json-native.h +++ b/source/compiler-core/slang-json-native.h @@ -12,6 +12,8 @@ namespace Slang { struct JSONToNativeConverter { SlangResult convert(const JSONValue& value, const RttiInfo* rttiInfo, void* out); + template <typename T> + SlangResult convert(const JSONValue& value, T* in) { return convert(value, GetRttiInfo<T>::get(), (void*)in); } JSONToNativeConverter(JSONContainer* container, DiagnosticSink* sink): m_container(container), @@ -32,6 +34,9 @@ struct NativeToJSONConverter { SlangResult convert(const RttiInfo* rttiInfo, const void* in, JSONValue& out); + template <typename T> + SlangResult convert(T* in, JSONValue& out) { return convert(GetRttiInfo<T>::get(), (const void*)in, out); } + NativeToJSONConverter(JSONContainer* container, DiagnosticSink* sink) : m_container(container), m_sink(sink) diff --git a/source/compiler-core/slang-json-rpc-connection.cpp b/source/compiler-core/slang-json-rpc-connection.cpp new file mode 100644 index 000000000..20283070b --- /dev/null +++ b/source/compiler-core/slang-json-rpc-connection.cpp @@ -0,0 +1,286 @@ +// slang-json-rpc-connection.cpp +#include "slang-json-rpc-connection.h" + +#include "../core/slang-string-util.h" +#include "../core/slang-process-util.h" + +#include "slang-json-rpc.h" +#include "slang-json-native.h" + +namespace Slang { + +SlangResult JSONRPCConnection::init(HTTPPacketConnection* connection, Process* process) +{ + m_connection = connection; + m_process = process; + + m_sourceManager.initialize(nullptr, nullptr); + m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation); + m_container.setSourceManager(&m_sourceManager); + + return SLANG_OK; +} + +SlangResult JSONRPCConnection::initWithStdStreams(Process* process) +{ + RefPtr<Stream> stdinStream, stdoutStream; + + Process::getStdStream(Process::StreamType::StdIn, stdinStream); + Process::getStdStream(Process::StreamType::StdOut, stdoutStream); + + RefPtr<BufferedReadStream> readStream(new BufferedReadStream(stdinStream)); + + RefPtr<HTTPPacketConnection> connection = new HTTPPacketConnection(readStream, stdoutStream); + return init(connection, process); +} + +void JSONRPCConnection::clearBuffers() +{ + m_sourceManager.reset(); + m_diagnosticSink.reset(); + m_container.reset(); + m_jsonRoot.reset(); +} + +bool JSONRPCConnection::isActive() +{ + return m_connection->isActive() && (m_process == nullptr || !m_process->isTerminated()); +} + +JSONValue JSONRPCConnection::getMessageId() +{ + SLANG_ASSERT(hasMessage()); + return JSONRPCUtil::getId(&m_container, m_jsonRoot); +} + +void JSONRPCConnection::disconnect() +{ + if (m_process) + { + if (!m_process->isTerminated()) + { + if (m_connection) + { + // Send. If succeeded, wait + if (SLANG_SUCCEEDED(sendCall(UnownedStringSlice::fromLiteral("quit")))) + { + // Wait for termination + m_process->waitForTermination(m_terminationTimeOutInMs); + } + } + + if (!m_process->isTerminated()) + { + // Okay, just try terminating + m_process->waitForTermination(m_terminationTimeOutInMs); + } + + // Okay just kill it then + if (!m_process->isTerminated()) + { + m_process->kill(-1); + } + } + m_process.setNull(); + } + + m_connection.setNull(); +} + +SlangResult JSONRPCConnection::sendRPC(const RttiInfo* rttiInfo, const void* data) +{ + // Convert to JSON + NativeToJSONConverter converter(&m_container, &m_diagnosticSink); + JSONValue value; + + SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, data, value)); + + // Convert to text + JSONWriter writer(JSONWriter::IndentationStyle::Allman); + + m_container.traverseRecursively(value, &writer); + const StringBuilder& builder = writer.getBuilder(); + return m_connection->write(builder.getBuffer(), builder.getLength()); +} + +SlangResult JSONRPCConnection::sendError(JSONRPC::ErrorCode code) +{ + return sendError(code, m_diagnosticSink.outputBuffer.getUnownedSlice()); +} + +SlangResult JSONRPCConnection::sendError(JSONRPC::ErrorCode errorCode, const UnownedStringSlice& msg) +{ + JSONRPCErrorResponse errorResponse; + errorResponse.error.code = Int(errorCode); + errorResponse.error.message = msg; + + // TODO(JS): + // This is only appropriate if the sendError is for the current input message. + // We might want to add function that the client uses, which take the id as a parameter. + + if (m_jsonRoot.isValid()) + { + errorResponse.id = JSONRPCUtil::getId(&m_container, m_jsonRoot); + } + else + { + // If we don't have valid json, we set the id to be null per the spec + errorResponse.id = JSONValue::makeNull(); + } + + return sendRPC(&errorResponse); +} + +SlangResult JSONRPCConnection::toNativeOrSendError(const JSONValue& value, const RttiInfo* info, void* dst) +{ + m_diagnosticSink.outputBuffer.Clear(); + if (SLANG_FAILED(JSONRPCUtil::convertToNative(&m_container, value, &m_diagnosticSink, info, dst))) + { + return sendError(JSONRPC::ErrorCode::InvalidRequest); + } + return SLANG_OK; +} + +SlangResult JSONRPCConnection::sendCall(const UnownedStringSlice& method, const JSONValue& id) +{ + JSONRPCCall call; + call.id = id; + call.method = method; + + SLANG_RETURN_ON_FAIL(sendRPC(&call)); + return SLANG_OK; +} + +SlangResult JSONRPCConnection::sendResult(const RttiInfo* rttiInfo, const void* result, const JSONValue& id) +{ + JSONResultResponse response; + response.id = id; + + NativeToJSONConverter converter(&m_container, &m_diagnosticSink); + SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, result, response.result)); + + // Send the RPC + SLANG_RETURN_ON_FAIL(sendRPC(&response)); + return SLANG_OK; +} + +SlangResult JSONRPCConnection::sendCall(const UnownedStringSlice& method, const RttiInfo* argsRttiInfo, const void* args, const JSONValue& id) +{ + JSONRPCCall call; + call.id = id; + call.method = method; + + // Convert the args/params + NativeToJSONConverter converter(&m_container, &m_diagnosticSink); + SLANG_RETURN_ON_FAIL(converter.convert(argsRttiInfo, args, call.params)); + + // Send the RPC + SLANG_RETURN_ON_FAIL(sendRPC(&call)); + return SLANG_OK; +} + +SlangResult JSONRPCConnection::waitForResult(Int timeOutInMs) +{ + SLANG_RETURN_ON_FAIL(m_connection->waitForResult(timeOutInMs)); + return tryReadMessage(); +} + +SlangResult JSONRPCConnection::tryReadMessage() +{ + m_jsonRoot.reset(); + + SLANG_RETURN_ON_FAIL(m_connection->update()); + if (!m_connection->hasContent()) + { + return SLANG_OK; + } + + auto content = m_connection->getContent(); + UnownedStringSlice slice((const char*)content.begin(), content.getCount()); + + clearBuffers(); + + { + const SlangResult res = JSONRPCUtil::parseJSON(slice, &m_container, &m_diagnosticSink, m_jsonRoot); + + // Consume that content/packet + m_connection->consumeContent(); + if (SLANG_FAILED(res)) + { + return sendError(JSONRPC::ErrorCode::ParseError); + } + } + + return SLANG_OK; +} + +JSONRPCMessageType JSONRPCConnection::getMessageType() +{ + return JSONRPCUtil::getMessageType(&m_container, m_jsonRoot); +} + +SlangResult JSONRPCConnection::getMessage(const RttiInfo* rttiInfo, void* out) +{ + if (!hasMessage()) + { + return SLANG_FAIL; + } + + m_diagnosticSink.outputBuffer.Clear(); + JSONToNativeConverter converter(&m_container, &m_diagnosticSink); + + // Get the RPC response + JSONResultResponse resultResponse; + SLANG_RETURN_ON_FAIL(converter.convert(m_jsonRoot, &resultResponse)); + + // Convert the result in the response + SLANG_RETURN_ON_FAIL(converter.convert(resultResponse.result, rttiInfo, out)); + return SLANG_OK; +} + +SlangResult JSONRPCConnection::getMessageOrSendError(const RttiInfo* rttiInfo, void* out) +{ + if (!hasMessage()) + { + return SLANG_FAIL; + } + + const auto res = getMessage(rttiInfo, out); + if (SLANG_FAILED(res)) + { + return sendError(JSONRPC::ErrorCode::ParseError); + } + return res; +} + +SlangResult JSONRPCConnection::getRPC(const RttiInfo* rttiInfo, void* out) +{ + if (!hasMessage()) + { + return SLANG_FAIL; + } + + m_diagnosticSink.outputBuffer.Clear(); + JSONToNativeConverter converter(&m_container, &m_diagnosticSink); + + // Convert the result in the response + SLANG_RETURN_ON_FAIL(converter.convert(m_jsonRoot, rttiInfo, out)); + return SLANG_OK; +} + +SlangResult JSONRPCConnection::getRPCOrSendError(const RttiInfo* rttiInfo, void* out) +{ + if (!hasMessage()) + { + return SLANG_FAIL; + } + + const auto res = getRPC(rttiInfo, out); + if (SLANG_FAILED(res)) + { + return sendError(JSONRPC::ErrorCode::ParseError); + } + return res; +} + +} // namespcae Slang diff --git a/source/compiler-core/slang-json-rpc-connection.h b/source/compiler-core/slang-json-rpc-connection.h new file mode 100644 index 000000000..730635493 --- /dev/null +++ b/source/compiler-core/slang-json-rpc-connection.h @@ -0,0 +1,161 @@ +#ifndef SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H +#define SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H + +#include "../../source/core/slang-http.h" +#include "../../source/core/slang-process.h" + +#include "slang-diagnostic-sink.h" +#include "slang-source-loc.h" +#include "slang-json-value.h" +#include "slang-json-rpc.h" + +#include "slang-json-diagnostics.h" + +#include "slang-test-server-protocol.h" + +namespace Slang { + +/* TODO(JS): + +It's not really clear here, how to handle id handling. If a server can receive multiple messages, and then later send responses we need +a way to queue up ids. This is complicated by JSONValue is only valid whilst the backing JSONContainer holds it. + +We probably want to create some kind of Variant that can hold all of these values that can hold state independently. +*/ +class JSONRPCConnection : public RefObject +{ +public: + + /// An init function must be called before use + /// If a process is implementing the server it should be passed in if the process needs to shut down if the connection does + SlangResult init(HTTPPacketConnection* connection, Process* process = nullptr); + + /// Initialize using stdin/out streams for input/output. + SlangResult initWithStdStreams(Process* process = nullptr); + + /// Disconnect. May block while server shuts down + void disconnect(); + + /// Convert value to dst. Will write response on fails + SlangResult toNativeOrSendError(const JSONValue& value, const RttiInfo* info, void* dst); + template <typename T> + SlangResult toNativeOrSendError(const JSONValue& value, T* data) { return toNativeOrSendError(value, GetRttiInfo<T>::get(), data); } + + template <typename T> + SlangResult toValidNativeOrSendError(const JSONValue& value, T* data); + + /// Send a RPC response (ie should only be one of the JSONRPC classes) + SlangResult sendRPC(const RttiInfo* info, const void* data); + template <typename T> + SlangResult sendRPC(const T* data) { return sendRPC(GetRttiInfo<T>::get(), (const void*)data); } + + /// Send an error + SlangResult sendError(JSONRPC::ErrorCode code); + SlangResult sendError(JSONRPC::ErrorCode errorCode, const UnownedStringSlice& msg); + + /// Send a call + /// If no id is needed, id can just be invalid + SlangResult sendCall(const UnownedStringSlice& method, const RttiInfo* argsRttiInfo, const void* args, const JSONValue& id = JSONValue()); + template <typename T> + SlangResult sendCall(const UnownedStringSlice& method, const T* args, const JSONValue& id = JSONValue()) { return sendCall(method, GetRttiInfo<T>::get(), (const void*)args, id); } + /// + SlangResult sendCall(const UnownedStringSlice& method, const JSONValue& id = JSONValue()); + + template <typename T> + SlangResult sendResult(const T* result, const JSONValue& id = JSONValue()) { return sendResult(GetRttiInfo<T>::get(), (const void*)result, id); } + SlangResult sendResult(const RttiInfo* rttiInfo, const void* result, const JSONValue& id = JSONValue()); + + /// Try to read a message. Will return if message is not available. + SlangResult tryReadMessage(); + + /// Will block for message/result up to time + SlangResult waitForResult(Int timeOutInMs = -1); + + /// If we have an JSON-RPC message m_jsonRoot the root. + bool hasMessage() const { return m_jsonRoot.isValid(); } + + /// If there is a message returns kind of JSON RPC message + JSONRPCMessageType getMessageType(); + + /// Get JSON-RPC message (ie one of JSONRPC classes) + template <typename T> + SlangResult getRPC(T* out) { return getRPC(GetRttiInfo<T>::get(), (void*)out); } + SlangResult getRPC(const RttiInfo* rttiInfo, void* out); + + /// Get JSON-RPC message (ie one of JSONRPC prefixed classes) + /// If there is a message and there is a failure, will send an error response + template <typename T> + SlangResult getRPCOrSendError(T* out) { return getRPCOrSendError(GetRttiInfo<T>::get(), (void*)out); } + SlangResult getRPCOrSendError(const RttiInfo* rttiInfo, void* out); + + /// Get message (has to be part of JSONRPCResultResponse) + template <typename T> + SlangResult getMessage(T* out) { return getMessage(GetRttiInfo<T>::get(), (void*)out); } + SlangResult getMessage(const RttiInfo* rttiInfo, void* out); + + /// If there is a message and there is a failure, will send an error response + template <typename T> + SlangResult getMessageOrSendError(T* out) { return getMessageOrSendError(GetRttiInfo<T>::get(), (void*)out); } + SlangResult getMessageOrSendError(const RttiInfo* rttiInfo, void* out); + + /// Clears all the internal buffers (for JSON/Source/etc). + /// Happens automatically on tryReadMessage/readMessage + void clearBuffers(); + + /// True if this connection is active + bool isActive(); + + /// Get the id of the current message + JSONValue getMessageId(); + + /// Get the diagnostic sink. Can queue up errors before sending an error + DiagnosticSink* getSink() { return &m_diagnosticSink; } + + /// Dtor + ~JSONRPCConnection() { disconnect(); } + + /// Ctor + JSONRPCConnection():m_container(nullptr) {} + +protected: + RefPtr<Slang::Process> m_process; ///< Backing process (optional) + RefPtr<Slang::HTTPPacketConnection> m_connection; + + DiagnosticSink m_diagnosticSink; + + SourceManager m_sourceManager; + JSONContainer m_container; + + JSONValue m_jsonRoot; + + /// Default timeout is 10 seconds + Int m_timeOutInMs = 10 * 1000; + /// Termination timeout + Int m_terminationTimeOutInMs = 1 * 1000; +}; + +// --------------------------------------------------------------------------- +template <typename T> +SlangResult JSONRPCConnection::toValidNativeOrSendError(const JSONValue& value, T* data) +{ + const RttiInfo* rttiInfo = GetRttiInfo<T>::get(); + + SLANG_RETURN_ON_FAIL(toNativeOrSendError(value, rttiInfo, (void*)data)); + if (!data->isValid()) + { + // If it has a name add validation info + if (rttiInfo->isNamed()) + { + const NamedRttiInfo* namedRttiInfo = static_cast<const NamedRttiInfo*>(rttiInfo); + m_diagnosticSink.diagnose(SourceLoc(), JSONDiagnostics::argsAreInvalid, namedRttiInfo->m_name); + } + + return sendError(JSONRPC::ErrorCode::InvalidRequest); + } + return SLANG_OK; +} + +} // namespace Slang + +#endif // SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H + diff --git a/source/compiler-core/slang-json-rpc.cpp b/source/compiler-core/slang-json-rpc.cpp index 1ca55e9eb..660bbd4b7 100644 --- a/source/compiler-core/slang-json-rpc.cpp +++ b/source/compiler-core/slang-json-rpc.cpp @@ -2,23 +2,20 @@ #include "../../slang-com-helper.h" +#include "slang-json-native.h" + namespace Slang { // https://www.jsonrpc.org/specification -// m_sourceManager.initialize(nullptr, nullptr); -// m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation); -static const auto g_jsonRpc = UnownedStringSlice::fromLiteral("jsonrpc"); -static const auto g_jsonRpcVersion = UnownedStringSlice::fromLiteral("2.0"); -static const auto g_method = UnownedStringSlice::fromLiteral("method"); -static const auto g_id = UnownedStringSlice::fromLiteral("id"); -static const auto g_params = UnownedStringSlice::fromLiteral("params"); -static const auto g_code = UnownedStringSlice::fromLiteral("code"); -static const auto g_error = UnownedStringSlice::fromLiteral("error"); -static const auto g_message = UnownedStringSlice::fromLiteral("message"); +/* static */ const UnownedStringSlice JSONRPC::jsonRpc = UnownedStringSlice::fromLiteral("jsonrpc"); +/* static */const UnownedStringSlice JSONRPC::jsonRpcVersion = UnownedStringSlice::fromLiteral("2.0"); +/* static */const UnownedStringSlice JSONRPC::id = UnownedStringSlice::fromLiteral("id"); + static const auto g_result = UnownedStringSlice::fromLiteral("result"); -static const auto g_data = UnownedStringSlice::fromLiteral("data"); +static const auto g_error = UnownedStringSlice::fromLiteral("error"); +static const auto g_method = UnownedStringSlice::fromLiteral("method"); // Add the fields. // TODO(JS): This is a little verbose, and could be improved on with something like @@ -39,9 +36,10 @@ static const StructRttiInfo _makeJSONRPCErrorResponseRtti() JSONRPCErrorResponse obj; StructRttiBuilder builder(&obj, "JSONRPCErrorResponse", nullptr); + builder.addField("jsonrpc", &obj.jsonrpc); builder.addField("error", &obj.error); builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional); - builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional); return builder.make(); } @@ -52,9 +50,10 @@ static const StructRttiInfo _makeJSONRPCCallResponseRtti() JSONRPCCall obj; StructRttiBuilder builder(&obj, "JSONRPCCall", nullptr); + builder.addField("jsonrpc", &obj.jsonrpc); builder.addField("method", &obj.method); builder.addField("params", &obj.params, StructRttiInfo::Flag::Optional); - builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional); return builder.make(); } @@ -65,350 +64,44 @@ static const StructRttiInfo _makeJSONResultResponseResponseRtti() JSONResultResponse obj; StructRttiBuilder builder(&obj, "JSONResultResponse", nullptr); + builder.addField("jsonrpc", &obj.jsonrpc); builder.addField("result", &obj.result); - builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional); return builder.make(); } /* static */const StructRttiInfo JSONResultResponse::g_rttiInfo = _makeJSONResultResponseResponseRtti(); - -/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id) -{ - const Index maxPairs = 4; - JSONKeyValue pairs[maxPairs]; - - Index i = 0; - - // Version number is a string - pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); - pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method)); - pairs[i++] = JSONKeyValue::make(container->getKey(g_params), params); - - if (id >= 0) - { - pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); - } - - return container->createObject(pairs, i); -} - -/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, Int id) -{ - const Index maxPairs = 3; - JSONKeyValue pairs[maxPairs]; - Index i = 0; - // Version number is a string - pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); - pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method)); - - if (id >= 0) - { - pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); - } - - return container->createObject(pairs, i); -} - -/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data, Int id) -{ - // Set up the error value - JSONValue errorValue; - { - const Index maxPairs = 2; - JSONKeyValue pairs[maxPairs]; - Index i = 0; - - if (code != 0) - { - pairs[i++] = JSONKeyValue::make(container->getKey(g_code), JSONValue::makeInt(code)); - } - if (message.getLength() > 0) - { - pairs[i++] = JSONKeyValue::make(container->getKey(g_message), container->createString(message)); - } - errorValue = container->createObject(pairs, i); - } - - const Index maxPairs = 4; - JSONKeyValue pairs[maxPairs]; - Index i = 0; - - pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); - pairs[i++] = JSONKeyValue::make(container->getKey(g_error), errorValue); - - if (data.isValid()) - { - pairs[i++] = JSONKeyValue::make(container->getKey(g_data), data); - } - - if (id >= 0) - { - pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); - } - - return container->createObject(pairs, i); -} - - -/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data, Int id) -{ - return createErrorResponse(container, Index(code), message, data, id); -} - -/* static */JSONValue JSONRPCUtil::createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id) +/* static */JSONRPCMessageType JSONRPCUtil::getMessageType(JSONContainer* container, const JSONValue& value) { - const Index maxPairs = 3; - JSONKeyValue pairs[maxPairs]; - Index i = 0; - - pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); - pairs[i++] = JSONKeyValue::make(container->getKey(g_result), resultValue); - - if (id >= 0) - { - pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); - } - - return container->createObject(pairs, i); -} - -/* static */ JSONRPCUtil::ResponseType JSONRPCUtil::getResponseType(JSONContainer* container, const JSONValue& response) -{ - if (response.getKind() == JSONValue::Kind::Object) + if (value.getKind() == JSONValue::Kind::Object) { const JSONKey resultKey = container->findKey(g_result); const JSONKey errorKey = container->findKey(g_error); + const JSONKey methodKey = container->findKey(g_method); - auto pairs = container->getObject(response); + auto pairs = container->getObject(value); for (const auto& pair : pairs) { if (pair.key == resultKey) { - return ResponseType::Result; + return JSONRPCMessageType::Result; } else if (pair.key == errorKey) { - return ResponseType::Error; - } - } - } - - return ResponseType::Error; -} - -static SlangResult _parseError(JSONContainer* container, const JSONValue& error, JSONRPCUtil::ErrorResponse& out) -{ - if (error.getKind() != JSONValue::Kind::Object) - { - return SLANG_FAIL; - } - const auto pairs = container->getObject(error); - - const JSONKey messageKey = container->findKey(g_message); - const JSONKey codeKey = container->findKey(g_code); - const JSONKey dataKey = container->findKey(g_data); - - Int fieldBits = 0; - - for (auto const& pair : pairs) - { - if (pair.key == messageKey) - { - if (pair.value.getKind() != JSONValue::Kind::String) - { - return SLANG_FAIL; - } - out.message = container->getString(pair.value); - fieldBits |= 0x1; - } - else if (pair.key == codeKey) - { - if (pair.value.getKind() != JSONValue::Kind::Integer) - { - return SLANG_FAIL; - } - out.code = Index(pair.value.asInteger()); - fieldBits |= 0x2; - } - else if (pair.key == dataKey) - { - out.data = pair.value; - fieldBits |= 0x4; - } - else - { - return SLANG_FAIL; - } - } - - // Check all required fields are set - return (fieldBits & 0x3) == 0x3 ? SLANG_OK : SLANG_FAIL; -} - -/* static */SlangResult JSONRPCUtil::parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out) -{ - if (response.getKind() != JSONValue::Kind::Object) - { - return SLANG_FAIL; - } - - const auto pairs = container->getObject(response); - - const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); - const JSONKey errorKey = container->findKey(g_error); - const JSONKey idKey = container->findKey(g_id); - - Int fieldBits = 0; - - for (auto const& pair : pairs) - { - if (pair.key == jsonRpcKey) - { - if (!container->areEqual(pair.value, g_jsonRpcVersion)) - { - return SLANG_FAIL; - } - fieldBits |= 0x1; - } - else if (pair.key == errorKey) - { - // We need to decode the error - SLANG_RETURN_ON_FAIL(_parseError(container, pair.value, out)); - fieldBits |= 0x2; - } - else if (pair.key == idKey) - { - if (pair.value.getKind() != JSONValue::Kind::Integer) - { - return SLANG_FAIL; - } - out.id = Int(pair.value.asInteger()); - fieldBits |= 0x4; - } - else - { - // Unknown key - return SLANG_FAIL; - } - } - - // Check all the required bits are set - return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; -} - -/* static */SlangResult JSONRPCUtil::parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out) -{ - if (response.getKind() != JSONValue::Kind::Object) - { - return SLANG_FAIL; - } - - const auto pairs = container->getObject(response); - - const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); - const JSONKey resultKey = container->findKey(g_result); - const JSONKey idKey = container->findKey(g_id); - - Int fieldBits = 0; - - for (auto const& pair : pairs) - { - if (pair.key == jsonRpcKey) - { - if (!container->areEqual(pair.value, g_jsonRpcVersion)) - { - return SLANG_FAIL; - } - fieldBits |= 0x1; - } - else if (pair.key == resultKey) - { - out.result = pair.value; - fieldBits |= 0x2; - } - else if (pair.key == idKey) - { - if (pair.value.getKind() != JSONValue::Kind::Integer) - { - return SLANG_FAIL; - } - out.id = Int(pair.value.asInteger()); - fieldBits |= 0x4; - } - else - { - // Unknown key - return SLANG_FAIL; - } - } - - // Check all the required bits are set - return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; -} - -/* static */SlangResult JSONRPCUtil::parseCall(JSONContainer* container, const JSONValue& value, Call& out) -{ - if (value.getKind() != JSONValue::Kind::Object) - { - return SLANG_FAIL; - } - - const auto pairs = container->getObject(value); - - const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); - const JSONKey methodKey = container->findKey(g_method); - const JSONKey paramsKey = container->findKey(g_params); - const JSONKey idKey = container->findKey(g_id); - - Int fieldBits = 0; - - for (auto const& pair : pairs) - { - if (pair.key == jsonRpcKey) - { - if (!container->areEqual(pair.value, g_jsonRpcVersion)) - { - return SLANG_FAIL; + return JSONRPCMessageType::Error; } - fieldBits |= 0x1; - } - else if (pair.key == methodKey) - { - if (pair.value.getKind() != JSONValue::Kind::String) - { - return SLANG_FAIL; - } - out.method = container->getString(pair.value); - fieldBits |= 0x2; - } - else if (pair.key == idKey) - { - if (pair.value.getKind() != JSONValue::Kind::Integer) + else if (pair.key == methodKey) { - return SLANG_FAIL; + return JSONRPCMessageType::Call; } - out.id = Int(pair.value.asInteger()); - fieldBits |= 0x4; - } - else if (pair.key == paramsKey) - { - out.params = pair.value; - fieldBits |= 0x8; - } - else - { - // Unknown key - return SLANG_FAIL; } } - // Check all the required bits are set - return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; + return JSONRPCMessageType::Invalid; } - /* static */SlangResult JSONRPCUtil::parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue) { SourceManager* sourceManager = sink->getSourceManager(); @@ -430,21 +123,50 @@ static SlangResult _parseError(JSONContainer* container, const JSONValue& error, return SLANG_OK; } -SlangResult JSONRPCUtil::parseJSONAndConsume(HTTPPacketConnection* connection, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue) +/* static */SlangResult JSONRPCUtil::convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, const RttiInfo* rttiInfo, void* out) { - if (!connection->hasContent()) - { - return SLANG_FAIL; - } + JSONToNativeConverter converter(container, sink); + SLANG_RETURN_ON_FAIL(converter.convert(value, rttiInfo, out)); + return SLANG_OK; +} - auto content = connection->getContent(); +/* static */SlangResult JSONRPCUtil::convertToJSON(const RttiInfo* rttiInfo, const void* in, DiagnosticSink* sink, StringBuilder& out) +{ + SourceManager* sourceManager = sink->getSourceManager(); + JSONContainer container(sourceManager); - UnownedStringSlice text((const char*)content.begin(), content.getCount()); - SlangResult res = parseJSON(text, container, sink, outValue); + NativeToJSONConverter converter(&container, sink); - // Consume the content - connection->consumeContent(); - return res; + JSONValue value; + SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, in, value)); + + // Convert into a string + JSONWriter writer(JSONWriter::IndentationStyle::Allman); + container.traverseRecursively(value, &writer); + + out = writer.getBuilder(); + return SLANG_OK; } +/* static */JSONValue JSONRPCUtil::getId(JSONContainer* container, const JSONValue& root) +{ + if (root.getKind() == JSONValue::Kind::Object) + { + const JSONKey key = container->findKey(JSONRPC::id); + + if (key != JSONKey(0)) + { + auto obj = container->getObject(root); + Index index = obj.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; }); + + if (index >= 0) + { + return obj[index].value; + } + } + } + return JSONValue(); +} + + } // namespace Slang diff --git a/source/compiler-core/slang-json-rpc.h b/source/compiler-core/slang-json-rpc.h index e85664ceb..f32513b02 100644 --- a/source/compiler-core/slang-json-rpc.h +++ b/source/compiler-core/slang-json-rpc.h @@ -12,115 +12,124 @@ namespace Slang { +/// Struct to hold values associated with JSON-RPC +struct JSONRPC +{ + enum class ErrorCode + { + ParseError = -32700, ///< Invalid JSON was received by the server. + InvalidRequest = -32600, ///< The JSON sent is not a valid Request object. + MethodNotFound = -32601, ///< The method does not exist / is not available. + InvalidParams = -32602, ///< Invalid method parameter(s). + InternalError = -32603, ///< Internal JSON - RPC error. + + ServerImplStart = -32000, ///< Server implementation defined error range + ServerImplEnd = -32099, + }; + + static bool isIdOk(const JSONValue& value) + { + auto kind = value.getKind(); + switch (kind) + { + case JSONValue::Kind::Integer: + case JSONValue::Kind::Invalid: + case JSONValue::Kind::String: + { + return true; + } + } + return false; + } + + static const UnownedStringSlice jsonRpc; + static const UnownedStringSlice jsonRpcVersion; + static const UnownedStringSlice id; +}; + struct JSONRPCErrorResponse { struct Error { - Index code = 0; ///< Value from ErrorCode - UnownedStringSlice message; ///< Error message + bool isValid() const { return code != 0; } + Int code = 0; ///< Value from ErrorCode + UnownedStringSlice message; ///< Error message + static const StructRttiInfo g_rttiInfo; }; + bool isValid() const { return jsonrpc == JSONRPC::jsonRpcVersion && error.isValid() && JSONRPC::isIdOk(id); } + + UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion; Error error; - JSONValue data; - Int id = -1; ///< Id of initiating method or -1 if not set + JSONValue data; ///< Optional data describing the errro + JSONValue id; ///< Id associated with this request static const StructRttiInfo g_rttiInfo; }; struct JSONRPCCall { + bool isValid() const { return method.getLength() > 0 && jsonrpc == JSONRPC::jsonRpcVersion && JSONRPC::isIdOk(id); } + + UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion; UnownedStringSlice method; ///< The name of the method JSONValue params; ///< Can be invalid/array/object - Int id = -1; ///< Id associated with this request, or -1 if not set + JSONValue id; ///< Id associated with this request static const StructRttiInfo g_rttiInfo; }; struct JSONResultResponse { - JSONValue result; ///< The result value - Int id = -1; ///< Id of initiating method or -1 if not set + bool isValid() const { return jsonrpc == JSONRPC::jsonRpcVersion && JSONRPC::isIdOk(id); } + + UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion; + JSONValue result; ///< The result value + JSONValue id; ///< Id associated with this request static const StructRttiInfo g_rttiInfo; }; +enum class JSONRPCMessageType +{ + Invalid, + Result, + Call, + Error, + CountOf, +}; + /// Send and receive messages as JSON -/// -/// Strictly speaking should support ids, as strings or ids. Currently just supports with integer ids. -/// One way of dealing with this would be to just use JSONValue for ids, would allow invalid/string/integer and -/// a mechanism to compare/display etc. class JSONRPCUtil { public: - enum class ErrorCode - { - ParseError = -32700, ///< Invalid JSON was received by the server. - InvalidRequest = -32600, ///< The JSON sent is not a valid Request object. - MethodNotFound = -32601, ///< The method does not exist / is not available. - InvalidParams = -32602, ///< Invalid method parameter(s). - InternalError = -32603, ///< Internal JSON - RPC error. - - ServerImplStart = -32000, ///< Server implementation defined error range - ServerImplEnd = -32099, - }; - - enum class ResponseType - { - Invalid, - Error, - Result - }; - - struct ErrorResponse - { - Index code = 0; ///< Value from ErrorCode - UnownedStringSlice message; ///< Error message - JSONValue data; - Int id = -1; ///< Id of initiating method or -1 if not set - }; - - struct ResultResponse - { - JSONValue result; ///< The result value - Int id = -1; ///< Id of initiating method or -1 if not set - }; - - struct Call - { - UnownedStringSlice method; ///< The name of the method - JSONValue params; ///< Can be invalid/array/object - Int id = -1; ///< Id associated with this request, or -1 if not set - }; - - /// Parameters can be either named or via index. - static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id = -1); - /// Parameters can be either named or via index. - static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, Int id = -1); - - /// Create an error response - /// Code should typically be something in the ErrorCode range - static JSONValue createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1); - static JSONValue createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1); - /// Create a result response - static JSONValue createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id = -1); - /// Determine the response type - static ResponseType getResponseType(JSONContainer* container, const JSONValue& response); + static JSONRPCMessageType getMessageType(JSONContainer* container, const JSONValue& value); - static SlangResult parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out); + /// Parse slice into JSONContainer. outValue is the root of the hierarchy. + /// NOTE! Uses and *assumes* there is a source manager on the sink. outValue is likely only usable whilst the sourceManger is in scope + /// The sourceLoc can only be interpretted with the sourceLoc anyway + static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue); - static SlangResult parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out); + /// Convert value into out + static SlangResult convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, const RttiInfo* rttiInfo, void* out); + template <typename T> + static SlangResult convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, T& out) { return convertToNative(container, value, sink, GetRttiInfo<T>::get(), (void*)&out); } - static SlangResult parseCall(JSONContainer* container, const JSONValue& value, Call& out); + /// Convert to JSON + static SlangResult convertToJSON(const RttiInfo* rttiInfo, const void* in, DiagnosticSink* sink, StringBuilder& out); - /// Parse slice into JSONContainer. outValue is the root of the hierarchy. - static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue); + template <typename T> + static SlangResult convertToJSON(const T* in, DiagnosticSink* sink, StringBuilder& out) + { + return convertToJSON(GetRttiInfo<T>::get(), (const void*)in, sink, out); + } - /// Parse content from stream, and consume the packet - static SlangResult parseJSONAndConsume(HTTPPacketConnection* connection, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue); + /// Get an id directly from root (assumed id: is in root object definition). + static JSONValue getId(JSONContainer* container, const JSONValue& root); }; } // namespace Slang diff --git a/source/compiler-core/slang-json-value.cpp b/source/compiler-core/slang-json-value.cpp index 4fad3764f..216512407 100644 --- a/source/compiler-core/slang-json-value.cpp +++ b/source/compiler-core/slang-json-value.cpp @@ -164,6 +164,8 @@ void JSONContainer::reset() m_objectValues.clear(); _addRange(Range::Type::None, 0, 0); + + m_currentView = nullptr; } /* static */bool JSONContainer::areKeysUnique(const JSONKeyValue* keyValues, Index keyValueCount) diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h index 119479285..b94283d81 100644 --- a/source/compiler-core/slang-json-value.h +++ b/source/compiler-core/slang-json-value.h @@ -100,7 +100,7 @@ struct JSONValue /// Given a type return the associated kind static Kind getKindForType(Type type) { return g_typeToKind[Index(type)]; } - Type type; ///< The type of value + Type type = Type::Invalid; ///< The type of value SourceLoc loc; ///< The (optional) location in source of this value. union @@ -112,6 +112,7 @@ struct JSONValue JSONKey stringKey; ///< The pool key if it's a string }; + static const Kind g_typeToKind[Index(Type::CountOf)]; static const OtherRttiInfo g_rttiInfo; diff --git a/source/compiler-core/slang-test-server-protocol.cpp b/source/compiler-core/slang-test-server-protocol.cpp new file mode 100644 index 000000000..2a3bb3a3b --- /dev/null +++ b/source/compiler-core/slang-test-server-protocol.cpp @@ -0,0 +1,43 @@ +#include "slang-test-server-protocol.h" + +namespace TestServerProtocol { + +static const StructRttiInfo _makeExecuteUnitTestArgsRtti() +{ + ExecuteUnitTestArgs obj; + StructRttiBuilder builder(&obj, "TestServerProtocol::ExecuteUnitTestArgs", nullptr); + + builder.addField("moduleName", &obj.moduleName); + builder.addField("testName", &obj.testName); + builder.addField("enabledApis", &obj.enabledApis); + return builder.make(); +} +/* static */const UnownedStringSlice ExecuteUnitTestArgs::g_methodName = UnownedStringSlice::fromLiteral("unitTest"); +/* static */const StructRttiInfo ExecuteUnitTestArgs::g_rttiInfo = _makeExecuteUnitTestArgsRtti(); + +static const StructRttiInfo _makeExecuteToolTestArgsRtti() +{ + ExecuteToolTestArgs obj; + StructRttiBuilder builder(&obj, "TestServerProtocol::ExecuteToolTestArgs", nullptr); + builder.addField("toolName", &obj.toolName); + builder.addField("args", &obj.args); + return builder.make(); +} +/* static */const StructRttiInfo ExecuteToolTestArgs::g_rttiInfo = _makeExecuteToolTestArgsRtti(); +/* static */const UnownedStringSlice ExecuteToolTestArgs::g_methodName = UnownedStringSlice::fromLiteral("tool"); + +static const StructRttiInfo _makeExecutionResultRtti() +{ + ExecutionResult obj; + StructRttiBuilder builder(&obj, "TestServerProtocol::ExecutionResult", nullptr); + builder.addField("stdOut", &obj.stdOut); + builder.addField("stdError", &obj.stdError); + builder.addField("result", &obj.result); + builder.addField("returnCode", &obj.returnCode); + return builder.make(); +} +/* static */const StructRttiInfo ExecutionResult::g_rttiInfo = _makeExecutionResultRtti(); + +/* static */const UnownedStringSlice QuitArgs::g_methodName = UnownedStringSlice::fromLiteral("quit"); + +} // namespace TestServerProtocol diff --git a/source/compiler-core/slang-test-server-protocol.h b/source/compiler-core/slang-test-server-protocol.h new file mode 100644 index 000000000..8b61c4b39 --- /dev/null +++ b/source/compiler-core/slang-test-server-protocol.h @@ -0,0 +1,51 @@ +#ifndef SLANG_COMPILER_CORE_TEST_PROTOCOL_H +#define SLANG_COMPILER_CORE_TEST_PROTOCOL_H + +#include "../../slang.h" +#include "../../slang-com-helper.h" +#include "../../slang-com-ptr.h" + +#include "../core/slang-rtti-info.h" +#include "slang-json-value.h" + +namespace TestServerProtocol { + +using namespace Slang; + +struct ExecuteUnitTestArgs +{ + String moduleName; + String testName; + uint32_t enabledApis; + + static const UnownedStringSlice g_methodName; + static const StructRttiInfo g_rttiInfo; +}; + +struct ExecuteToolTestArgs +{ + String toolName; ///< The name of the tool (will be a shared library typically - like render-test). Doesn't need -tool suffix. + List<String> args; ///< Arguments passed to the tool during exectution + + static const UnownedStringSlice g_methodName; + static const StructRttiInfo g_rttiInfo; +}; + +struct QuitArgs +{ + static const UnownedStringSlice g_methodName; +}; + +struct ExecutionResult +{ + String stdOut; + String stdError; + int32_t result = SLANG_OK; + int32_t returnCode = 0; ///< As returned if invoked as command line + + static const StructRttiInfo g_rttiInfo; +}; + +} // namespace Slang + +#endif // SLANG_COMPILER_CORE_TEST_PROTOCOL_H diff --git a/source/core/slang-http.cpp b/source/core/slang-http.cpp index 43be26c1c..6a4bb4f92 100644 --- a/source/core/slang-http.cpp +++ b/source/core/slang-http.cpp @@ -287,8 +287,19 @@ SlangResult HTTPPacketConnection::update() return m_readResult; } -SlangResult HTTPPacketConnection::waitForResult() +SlangResult HTTPPacketConnection::waitForResult(Int timeOutInMs) { + m_readResult = SLANG_OK; + + int64_t startTick = 0; + int64_t timeOutInTicks = -1; + + if (timeOutInMs >= 0) + { + timeOutInTicks = timeOutInMs * (Process::getClockFrequency() / 1000); + startTick = Process::getClockTick(); + } + while (m_readState == ReadState::Header || m_readState == ReadState::Content) { @@ -296,6 +307,17 @@ SlangResult HTTPPacketConnection::waitForResult() SLANG_RETURN_ON_FAIL(update()); + if (m_readState == ReadState::Done) + { + break; + } + + // We timed out + if (timeOutInTicks >= 0 && int64_t(Process::getClockTick()) - startTick >= timeOutInTicks) + { + break; + } + if (prevCount == m_readStream->getCount()) { // Yield if it appears nothing was read. diff --git a/source/core/slang-http.h b/source/core/slang-http.h index a298e9e27..784357273 100644 --- a/source/core/slang-http.h +++ b/source/core/slang-http.h @@ -123,8 +123,9 @@ public: /// Write. Will potentially block if write stream is blocking. SlangResult write(const void* content, size_t sizeInBytes); - /// Blocks until some result - a packet, closure, or some kind of error - SlangResult waitForResult(); + /// Blocks until some result - a packet, closure, or some kind of error or timeout. + /// TimeOut of -1 means no timeout. + SlangResult waitForResult(Int timeOutInMs = -1); /// Consume the content - so can read next content void consumeContent(); diff --git a/source/core/slang-writer.h b/source/core/slang-writer.h index 380823b39..7c768a22f 100644 --- a/source/core/slang-writer.h +++ b/source/core/slang-writer.h @@ -148,6 +148,7 @@ public: Parent(flags), m_builder(builder) {} + ~StringWriter() {} protected: StringBuilder* m_builder; |
