diff options
22 files changed, 1101 insertions, 560 deletions
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj index 5e70d59f7..4a35f982a 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj @@ -288,6 +288,7 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-lexer.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-native.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h" />
+ <ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc-connection.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h" />
@@ -298,6 +299,7 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-name.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-nvrtc-compiler.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-source-loc.h" />
+ <ClInclude Include="..\..\..\source\compiler-core\slang-test-server-protocol.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-token-defs.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-token.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-visual-studio-compiler-util.h" />
@@ -317,6 +319,7 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-json-lexer.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-native.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp" />
+ <ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc-connection.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp" />
@@ -325,6 +328,7 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-name.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-nvrtc-compiler.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-source-loc.cpp" />
+ <ClCompile Include="..\..\..\source\compiler-core\slang-test-server-protocol.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-token.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-visual-studio-compiler-util.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\windows\slang-win-visual-studio-util.cpp" />
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters index c392612f6..0bb53f1b8 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters @@ -51,6 +51,9 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc-connection.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -81,6 +84,9 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-source-loc.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-test-server-protocol.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-token-defs.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -134,6 +140,9 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc-connection.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -158,6 +167,9 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-source-loc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-test-server-protocol.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-token.cpp">
<Filter>Source Files</Filter>
</ClCompile>
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; diff --git a/tools/slang-test/options.cpp b/tools/slang-test/options.cpp index d24ea83e2..4fe7ea6bd 100644 --- a/tools/slang-test/options.cpp +++ b/tools/slang-test/options.cpp @@ -81,6 +81,7 @@ static bool _isSubCommand(const char* arg) while (argCursor != argEnd) { char const* arg = *argCursor++; + if (arg[0] != '-') { // We need to determine if this is a command, the confusion is that @@ -129,6 +130,10 @@ static bool _isSubCommand(const char* arg) { optionsOut->defaultSpawnType = SpawnType::UseProxy; } + else if (strcmp(arg, "-use-test-server") == 0) + { + optionsOut->defaultSpawnType = SpawnType::UseTestServer; + } else if (strcmp(arg, "-v") == 0) { optionsOut->shouldBeVerbose = true; diff --git a/tools/slang-test/options.h b/tools/slang-test/options.h index 4a40cba9a..5dc3ca1fb 100644 --- a/tools/slang-test/options.h +++ b/tools/slang-test/options.h @@ -41,6 +41,7 @@ enum class SpawnType UseExe, UseSharedLibrary, UseProxy, + UseTestServer, }; struct Options diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index e007d1978..000ee462c 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -655,6 +655,62 @@ Result spawnAndWaitProxy(TestContext* context, const String& testPath, const Com return res; } +static Result _executeRPC(TestContext* context, const UnownedStringSlice& method, const RttiInfo* rttiInfo, const void* args, ExecuteResult& outRes) +{ + JSONRPCConnection* rpcConnection = context->getOrCreateJSONRPCConnection(); + if (!rpcConnection) + { + return SLANG_FAIL; + } + + // Execute + SLANG_RETURN_ON_FAIL(rpcConnection->sendCall(method, rttiInfo, args)); + + // Wait for the result + rpcConnection->waitForResult(context->timeOutInMs); + + if (!rpcConnection->hasMessage()) + { + // We can assume somethings gone wrong. So lets kill the connection and fail. + context->destroyRPCConnection(); + return SLANG_FAIL; + } + + if (rpcConnection->getMessageType() != JSONRPCMessageType::Result) + { + return SLANG_FAIL; + } + + // Get the result + TestServerProtocol::ExecutionResult exeRes; + SLANG_RETURN_ON_FAIL(rpcConnection->getMessage(&exeRes)); + + outRes.resultCode = exeRes.returnCode; + outRes.standardError = exeRes.stdError; + outRes.standardOutput = exeRes.stdOut; + + return SLANG_OK; +} + +template <typename T> +static Result _executeRPC(TestContext* context, const UnownedStringSlice& method, const T* msg, ExecuteResult& outRes) +{ + return _executeRPC(context, method, GetRttiInfo<T>::get(), (const void*)msg, outRes); +} + +Result spawnAndWaitTestServer(TestContext* context, const String& testPath, const CommandLine& inCmdLine, ExecuteResult& outRes) +{ + String exeName = Path::getFileNameWithoutExt(inCmdLine.m_executable); + + // This is a test tool execution + TestServerProtocol::ExecuteToolTestArgs args; + + args.toolName = exeName; + args.args = inCmdLine.m_args; + + return _executeRPC(context, TestServerProtocol::ExecuteToolTestArgs::g_methodName, &args, outRes); +} + static SlangResult _extractArg(const CommandLine& cmdLine, const String& argName, String& outValue) { SLANG_ASSERT(argName.getLength() > 0 && argName[0] == '-'); @@ -1012,6 +1068,11 @@ ToolReturnCode spawnAndWait(TestContext* context, const String& testPath, SpawnT spawnResult = spawnAndWaitProxy(context, testPath, cmdLine, outExeRes); break; } + case SpawnType::UseTestServer: + { + spawnResult = spawnAndWaitTestServer(context, testPath, cmdLine, outExeRes); + break; + } default: break; } @@ -1574,7 +1635,7 @@ TestResult runReflectionTest(TestContext* context, TestInput& input) TestResult result = TestResult::Pass; // Otherwise we compare to the expected output - if (actualOutput != expectedOutput) + if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice())) { result = TestResult::Fail; } @@ -1981,7 +2042,7 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input) TestResult result = TestResult::Pass; // Otherwise we compare to the expected output - if (actualOutput != expectedOutput) + if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice())) { result = TestResult::Fail; } @@ -2135,7 +2196,7 @@ static TestResult _runHLSLComparisonTest( if (standardOutput.getLength() != 0) result = TestResult::Fail; } // Otherwise we compare to the expected output - else if (actualOutput != expectedOutput) + else if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice())) { result = TestResult::Fail; } @@ -2272,7 +2333,7 @@ TestResult runGLSLComparisonTest(TestContext* context, TestInput& input) if( hlslResult == TestResult::Fail ) return TestResult::Fail; if( slangResult == TestResult::Fail ) return TestResult::Fail; - if (actualOutput != expectedOutput) + if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice())) { context->reporter->dumpOutputDifference(expectedOutput, actualOutput); @@ -2511,7 +2572,8 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons auto actualOutput = getOutput(exeRes); auto expectedOutput = getExpectedOutput(outputStem); - if (actualOutput != expectedOutput) + + if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice())) { context->reporter->dumpOutputDifference(expectedOutput, actualOutput); @@ -2817,7 +2879,7 @@ TestResult runHLSLRenderComparisonTestImpl( if( hlslResult == TestResult::Fail ) return TestResult::Fail; if( slangResult == TestResult::Fail ) return TestResult::Fail; - if (actualOutput != expectedOutput) + if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice())) { context->reporter->dumpOutputDifference(expectedOutput, actualOutput); @@ -3478,6 +3540,30 @@ static SlangResult runUnitTestModule(TestContext* context, TestOptions& testOpti reporter->addResult(testResult); } } + else if (spawnType == SpawnType::UseTestServer) + { + TestServerProtocol::ExecuteUnitTestArgs args; + args.enabledApis = context->options.enabledApis; + args.moduleName = moduleName; + args.testName = testName; + + { + TestReporter::TestScope scopeTest(reporter, testOptions.command); + ExecuteResult exeRes; + + SlangResult rpcRes = _executeRPC(context, TestServerProtocol::ExecuteUnitTestArgs::g_methodName, &args, exeRes); + const auto testResult = _asTestResult(ToolReturnCode(exeRes.resultCode)); + + // If the test fails, output any output - which might give information about individual tests that have failed. + if (SLANG_FAILED(rpcRes) || testResult == TestResult::Fail) + { + String output = getOutput(exeRes); + reporter->message(TestMessageType::TestFailure, output.getBuffer()); + } + + reporter->addResult(testResult); + } + } else { TestReporter::TestScope scopeTest(reporter, testOptions.command); diff --git a/tools/slang-test/test-context.cpp b/tools/slang-test/test-context.cpp index 2872ddb47..eeb23b695 100644 --- a/tools/slang-test/test-context.cpp +++ b/tools/slang-test/test-context.cpp @@ -106,6 +106,53 @@ DownstreamCompilerSet* TestContext::getCompilerSet() return compilerSet; } +SlangResult TestContext::_createJSONRPCConnection(RefPtr<JSONRPCConnection>& out) +{ + RefPtr<Process> process; + + { + CommandLine cmdLine; + cmdLine.setExecutable(exeDirectoryPath.getBuffer(), "test-server"); + SLANG_RETURN_ON_FAIL(Process::create(cmdLine, Process::Flag::AttachDebugger, process)); + } + + Stream* writeStream = process->getStream(Process::StreamType::StdIn); + RefPtr<BufferedReadStream> readStream(new BufferedReadStream(process->getStream(Process::StreamType::StdOut))); + + RefPtr<HTTPPacketConnection> connection = new HTTPPacketConnection(readStream, writeStream); + RefPtr<JSONRPCConnection> rpcConnection = new JSONRPCConnection; + + SLANG_RETURN_ON_FAIL(rpcConnection->init(connection, process)); + + out = rpcConnection; + + return SLANG_OK; +} + + +void TestContext::destroyRPCConnection() +{ + if (m_jsonRpcConnection) + { + m_jsonRpcConnection->disconnect(); + m_jsonRpcConnection.setNull(); + } +} + +Slang::JSONRPCConnection* TestContext::getOrCreateJSONRPCConnection() +{ + if (!m_jsonRpcConnection) + { + if (SLANG_FAILED(_createJSONRPCConnection(m_jsonRpcConnection))) + { + return nullptr; + } + } + + return m_jsonRpcConnection; +} + + Slang::DownstreamCompiler* TestContext::getDefaultCompiler(SlangSourceLanguage sourceLanguage) { DownstreamCompilerSet* set = getCompilerSet(); diff --git a/tools/slang-test/test-context.h b/tools/slang-test/test-context.h index 67707028d..203005943 100644 --- a/tools/slang-test/test-context.h +++ b/tools/slang-test/test-context.h @@ -11,6 +11,7 @@ #include "../../source/core/slang-render-api-util.h" #include "../../source/compiler-core/slang-downstream-compiler.h" +#include "../../source/compiler-core/slang-json-rpc-connection.h" #include "../../slang-com-ptr.h" @@ -110,6 +111,9 @@ class TestContext Slang::DownstreamCompilerSet* getCompilerSet(); Slang::DownstreamCompiler* getDefaultCompiler(SlangSourceLanguage sourceLanguage); + Slang::JSONRPCConnection* getOrCreateJSONRPCConnection(); + void destroyRPCConnection(); + /// Ctor TestContext(); /// Dtor @@ -129,14 +133,21 @@ class TestContext Slang::RefPtr<Slang::DownstreamCompilerSet> compilerSet; Slang::String exeDirectoryPath; - + + Slang::Int timeOutInMs = 100 * 1000; + + protected: + SlangResult _createJSONRPCConnection(Slang::RefPtr<Slang::JSONRPCConnection>& out); + struct SharedLibraryTool { Slang::ComPtr<ISlangSharedLibrary> m_sharedLibrary; InnerMainFunc m_func; }; + Slang::RefPtr<Slang::JSONRPCConnection> m_jsonRpcConnection; + SlangSession* m_session; Slang::Dictionary<Slang::String, SharedLibraryTool> m_sharedLibTools; diff --git a/tools/test-server/test-server-diagnostic-defs.h b/tools/test-server/test-server-diagnostic-defs.h index ef0b280e8..8eedaba0d 100644 --- a/tools/test-server/test-server-diagnostic-defs.h +++ b/tools/test-server/test-server-diagnostic-defs.h @@ -22,5 +22,6 @@ DIAGNOSTIC(100000, Error, unableToLoadSharedLibrary, "Unable to load shared libr DIAGNOSTIC(100001, Error, unableToFindFunctionInSharedLibrary, "Unable to find function '$0' in shared library") DIAGNOSTIC(100002, Error, unableToGetUnitTestModule, "Unable to get unit test module") DIAGNOSTIC(100003, Error, unableToFindTest, "Unable to find test '$0'") +DIAGNOSTIC(100004, Error, unableToFindUnitTestModule, "Unable to find unit test module '$0'") #undef DIAGNOSTIC diff --git a/tools/test-server/test-server-main.cpp b/tools/test-server/test-server-main.cpp index 7e4180441..2a2f65f31 100644 --- a/tools/test-server/test-server-main.cpp +++ b/tools/test-server/test-server-main.cpp @@ -17,14 +17,10 @@ #include "../../source/core/slang-shared-library.h" #include "../../source/core/slang-test-tool-util.h" -#include "../../source/core/slang-http.h" -#include "../../source/compiler-core/slang-source-loc.h" -#include "../../source/compiler-core/slang-diagnostic-sink.h" +#include "../../source/compiler-core/slang-json-rpc-connection.h" -#include "../../source/compiler-core/slang-json-parser.h" -#include "../../source/compiler-core/slang-json-rpc.h" -#include "../../source/compiler-core/slang-json-value.h" +#include "../../source/compiler-core/slang-test-server-protocol.h" #include "test-server-diagnostics.h" @@ -59,7 +55,7 @@ public: SlangResult init(int argc, const char* const* argv); /// Can return nullptr if cannot create the session - slang::IGlobalSession* getGlobalSession(); + slang::IGlobalSession* getOrCreateGlobalSession(); /// Can return nullptr if cannot load the tool ISlangSharedLibrary* loadSharedLibrary(const String& name, DiagnosticSink* sink = nullptr); @@ -78,43 +74,111 @@ public: protected: SlangResult _executeSingle(); - SlangResult _executeUnitTest(JSONContainer* container, const JSONValue& root); - SlangResult _executeTool(JSONContainer* container, const JSONValue& root); - SlangResult _writeResponse(JSONContainer* containers, const JSONValue& root); + SlangResult _executeUnitTest(const JSONRPCCall& call); + SlangResult _executeTool(const JSONRPCCall& root); bool m_quit = false; - ComPtr<slang::IGlobalSession> m_session; + ComPtr<slang::IGlobalSession> m_session; /// The slang session. Is created on demand - Dictionary<String, ComPtr<ISlangSharedLibrary>> m_sharedLibraryMap; ///< Maps tool names to the dll - Dictionary<String, IUnitTestModule*> m_unitTestModules; + Dictionary<String, ComPtr<ISlangSharedLibrary>> m_sharedLibraryMap; ///< Maps tool names to the dll + Dictionary<String, IUnitTestModule*> m_unitTestModules; ///< All the unit test modules. - String m_exePath; ///< Path to executable + String m_exePath; ///< Path to executable (including exe name) + String m_exeDirectory; ///< The directory that holds the exe - DiagnosticSink m_diagnosticSink; - SourceManager m_sourceManager; - - RefPtr<HTTPPacketConnection> m_connection; + RefPtr<JSONRPCConnection> m_connection; ///< RPC connection, recieves calls to execute and returns results via JSON-RPC }; /* !!!!!!!!!!!!!!!!!!!!!!!!!!!! TestServer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ -SlangResult TestServer::init(int argc, const char* const* argv) +namespace SlangCTool { + +static void _diagnosticCallback(char const* message, void* userData) +{ + ISlangWriter* writer = (ISlangWriter*)userData; + writer->write(message, strlen(message)); +} + +SlangResult innerMain(StdWriters* stdWriters, slang::IGlobalSession* sharedSession, int argc, const char* const* argv) { - m_exePath = Path::getParentDirectory(argv[0]); + // Assume we will used the shared session + ComPtr<slang::IGlobalSession> session(sharedSession); + + // The sharedSession always has a pre-loaded stdlib. + // This differed test checks if the command line has an option to setup the stdlib. + // If so we *don't* use the sharedSession, and create a new stdlib-less session just for this compilation. + if (TestToolUtil::hasDeferredStdLib(Index(argc - 1), argv + 1)) + { + SLANG_RETURN_ON_FAIL(slang_createGlobalSessionWithoutStdLib(SLANG_API_VERSION, session.writeRef())); + } + + ComPtr<slang::ICompileRequest> compileRequest; + SLANG_RETURN_ON_FAIL(session->createCompileRequest(compileRequest.writeRef())); + + // Do any app specific configuration + for (int i = 0; i < SLANG_WRITER_CHANNEL_COUNT_OF; ++i) + { + compileRequest->setWriter(SlangWriterChannel(i), stdWriters->getWriter(i)); + } + + compileRequest->setDiagnosticCallback(&_diagnosticCallback, stdWriters->getWriter(SLANG_WRITER_CHANNEL_STD_ERROR)); + compileRequest->setCommandLineCompilerMode(); + + { + const SlangResult res = compileRequest->processCommandLineArguments(&argv[1], argc - 1); + if (SLANG_FAILED(res)) + { + // TODO: print usage message + return res; + } + } + + SlangResult compileRes = SLANG_OK; + +#ifndef _DEBUG + try +#endif + { + // Run the compiler (this will produce any diagnostics through SLANG_WRITER_TARGET_TYPE_DIAGNOSTIC). + compileRes = compileRequest->compile(); + + // If the compilation failed, then get out of here... + // Turn into an internal Result -> such that return code can be used to vary result to match previous behavior + compileRes = SLANG_FAILED(compileRes) ? SLANG_E_INTERNAL_FAIL : compileRes; + } +#ifndef _DEBUG + catch (const Exception& e) + { + WriterHelper writerHelper(stdWriters->getWriter(SLANG_WRITER_CHANNEL_STD_OUTPUT)); + writerHelper.print("internal compiler error: %S\n", e.Message.toWString().begin()); + compileRes = SLANG_FAIL; + } +#endif - RefPtr<Stream> stdinStream, stdoutStream; + return compileRes; +} - Process::getStdStream(Process::StreamType::StdIn, stdinStream); - Process::getStdStream(Process::StreamType::StdOut, stdoutStream); +} // namespace SlangCTool - RefPtr<BufferedReadStream> readStream(new BufferedReadStream(stdinStream)); +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! TestServer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ - m_connection = new HTTPPacketConnection(readStream, stdoutStream); +SlangResult TestServer::init(int argc, const char* const* argv) +{ + m_exePath = argv[0]; - m_sourceManager.initialize(nullptr, nullptr); - m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation); + String canonicalPath; + if (SLANG_SUCCEEDED(Path::getCanonical(m_exePath, canonicalPath))) + { + m_exeDirectory = Path::getParentDirectory(canonicalPath); + } + else + { + m_exeDirectory = Path::getParentDirectory(m_exePath); + } + m_connection = new JSONRPCConnection; + SLANG_RETURN_ON_FAIL(m_connection->initWithStdStreams()); return SLANG_OK; } @@ -126,7 +190,7 @@ TestServer::~TestServer() } } -slang::IGlobalSession* TestServer::getGlobalSession() +slang::IGlobalSession* TestServer::getOrCreateGlobalSession() { if (!m_session) { @@ -151,7 +215,7 @@ ISlangSharedLibrary* TestServer::loadSharedLibrary(const String& name, Diagnosti auto loader = DefaultSharedLibraryLoader::getSingleton(); - auto toolPath = Path::combine(m_exePath, name); + auto toolPath = Path::combine(m_exeDirectory, name); ComPtr<ISlangSharedLibrary> sharedLibrary; if (SLANG_FAILED(loader->loadSharedLibrary(toolPath.getBuffer(), sharedLibrary.writeRef()))) @@ -182,10 +246,10 @@ IUnitTestModule* TestServer::getUnitTestModule(const String& name, DiagnosticSin return nullptr; } - UnownedStringSlice funcName = UnownedStringSlice::fromLiteral("slangUnitTestGetModule"); + const char funcName[] = "slangUnitTestGetModule"; // get the unit test export name - UnitTestGetModuleFunc getModuleFunc = (UnitTestGetModuleFunc)sharedLibrary->findFuncByName(funcName.begin()); + UnitTestGetModuleFunc getModuleFunc = (UnitTestGetModuleFunc)sharedLibrary->findFuncByName(funcName); if (!getModuleFunc) { if (sink) @@ -211,6 +275,11 @@ IUnitTestModule* TestServer::getUnitTestModule(const String& name, DiagnosticSin TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, DiagnosticSink* sink) { + if (name == "slangc") + { + return &SlangCTool::innerMain; + } + StringBuilder sharedLibToolBuilder; sharedLibToolBuilder.append(name); sharedLibToolBuilder.append("-tool"); @@ -221,9 +290,9 @@ TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, Diagno return nullptr; } - UnownedStringSlice funcName = UnownedStringSlice::fromLiteral("innerMain"); + const char funcName[] = "innerMain"; - auto func = (InnerMainFunc)sharedLibrary->findFuncByName(funcName.begin()); + auto func = (InnerMainFunc)sharedLibrary->findFuncByName(funcName); if (!func && sink) { sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindFunctionInSharedLibrary, funcName); @@ -232,79 +301,54 @@ TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, Diagno return func; } -SlangResult TestServer::_writeResponse(JSONContainer* container, const JSONValue& root) -{ - // TODO(JS): We may want a non indented style, to reduce size - JSONWriter writer(JSONWriter::IndentationStyle::Allman); - container->traverseRecursively(root, &writer); - const StringBuilder& builder = writer.getBuilder(); - return m_connection->write(builder.getBuffer(), builder.getLength()); -} - SlangResult TestServer::_executeSingle() { // Block waiting for content (or error/closed) SLANG_RETURN_ON_FAIL(m_connection->waitForResult()); - // If we don't have content, we can quit for now - if (!m_connection->hasContent()) + // If we don't have a message, we can quit for now + if (!m_connection->hasMessage()) { return SLANG_OK; } - auto content = m_connection->getContent(); - - UnownedStringSlice slice((const char*)content.begin(), content.getCount()); - - // Reset for parse - m_sourceManager.reset(); - m_diagnosticSink.reset(); - - JSONContainer container(&m_sourceManager); - - // Parse as RPC JSON - JSONValue root; + const JSONRPCMessageType msgType = m_connection->getMessageType(); + switch (msgType) { - SlangResult res = JSONRPCUtil::parseJSON(slice, &container, &m_diagnosticSink, root); - // Consume that content/packet - m_connection->consumeContent(); - - if (SLANG_FAILED(res)) + case JSONRPCMessageType::Call: { - return _writeResponse(&container, JSONRPCUtil::createErrorResponse(&container, JSONRPCUtil::ErrorCode::InvalidRequest, UnownedStringSlice::fromLiteral("Unable to parse JSON"))); + JSONRPCCall call; + SLANG_RETURN_ON_FAIL(m_connection->getRPCOrSendError(&call)); + + // Do different things + if (call.method == TestServerProtocol::QuitArgs::g_methodName) + { + m_quit = true; + return SLANG_OK; + } + else if (call.method == TestServerProtocol::ExecuteUnitTestArgs::g_methodName) + { + SLANG_RETURN_ON_FAIL(_executeUnitTest(call)); + return SLANG_OK; + } + else if (call.method == TestServerProtocol::ExecuteToolTestArgs::g_methodName) + { + SLANG_RETURN_ON_FAIL(_executeTool(call)); + break; + } + else + { + return m_connection->sendError(JSONRPC::ErrorCode::MethodNotFound); + } } - } - - JSONRPCUtil::Call call; - { - SlangResult res = JSONRPCUtil::parseCall(&container, root, call); - if (SLANG_FAILED(res)) + default: { - return _writeResponse(&container, JSONRPCUtil::createErrorResponse(&container, Index(JSONRPCUtil::ErrorCode::InvalidRequest), UnownedStringSlice::fromLiteral("Cannot parse call"))); + return m_connection->sendError(JSONRPC::ErrorCode::ParseError); } } - const auto& method = call.method; - - // Do different things - if (method == "quit") - { - m_quit = true; - return SLANG_OK; - } - else if (method == "unitTest") - { - SLANG_RETURN_ON_FAIL(_executeUnitTest(&container, root)); - return SLANG_OK; - } - else if (method == "tool") - { - SLANG_RETURN_ON_FAIL(_executeTool(&container, root)); - return SLANG_OK; - } - - return SLANG_FAIL; + return SLANG_OK; } static Index _findTestIndex(IUnitTestModule* testModule, const String& name) @@ -322,23 +366,25 @@ static Index _findTestIndex(IUnitTestModule* testModule, const String& name) return -1; } -SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONValue& root) +SlangResult TestServer::_executeUnitTest(const JSONRPCCall& call) { - String moduleName; - String testName; - Int enabledApis = 0; + TestServerProtocol::ExecuteUnitTestArgs args; + SLANG_RETURN_ON_FAIL(m_connection->toNativeOrSendError(call.params, &args)); - IUnitTestModule* testModule = getUnitTestModule(moduleName, &m_diagnosticSink); + auto sink = m_connection->getSink(); + + IUnitTestModule* testModule = getUnitTestModule(args.moduleName, m_connection->getSink()); if (!testModule) { - return SLANG_FAIL; + sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindUnitTestModule, args.moduleName); + return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams); } - Index testIndex = _findTestIndex(testModule, moduleName); + const Index testIndex = _findTestIndex(testModule, args.testName); if (testIndex < 0) { - m_diagnosticSink.diagnose(SourceLoc(), ServerDiagnostics::unableToFindTest, testName); - return SLANG_FAIL; + sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindTest, args.testName); + return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams); } TestReporter testReporter; @@ -346,7 +392,7 @@ SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONVal testModule->setTestReporter(&testReporter); // Assume we will used the shared session - slang::IGlobalSession* session = getGlobalSession(); + slang::IGlobalSession* session = getOrCreateGlobalSession(); if (!session) { return SLANG_FAIL; @@ -355,8 +401,8 @@ SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONVal UnitTestContext unitTestContext; unitTestContext.slangGlobalSession = session; unitTestContext.workDirectory = ""; - unitTestContext.enabledApis = RenderApiFlags(enabledApis); - unitTestContext.executableDirectory = m_exePath.getBuffer(); + unitTestContext.enabledApis = RenderApiFlags(args.enabledApis); + unitTestContext.executableDirectory = m_exeDirectory.getBuffer(); auto testCount = testModule->getTestCount(); SLANG_ASSERT(testIndex >= 0 && testIndex < testCount); @@ -372,73 +418,90 @@ SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONVal testReporter.m_failCount++; } + TestServerProtocol::ExecutionResult result; + result.result = SLANG_OK; + if (testReporter.m_failCount > 0) { - // Write out to stderr... - auto writers = StdWriters::createDefault(); - writers->getError().put(testReporter.m_buf.getUnownedSlice()); - return SLANG_FAIL; + result.result = SLANG_FAIL; + result.stdError = testReporter.m_buf.getUnownedSlice(); } - - if (testReporter.m_testCount == 0) + else if (testReporter.m_testCount == 0) { - return SLANG_E_NOT_AVAILABLE; + result.result = SLANG_E_NOT_AVAILABLE; } - return SLANG_OK; + result.returnCode = int32_t(TestToolUtil::getReturnCode(result.result)); + return m_connection->sendResult(&result, m_connection->getMessageId()); } -SlangResult TestServer::_executeTool(JSONContainer* container, const JSONValue& root) +SlangResult TestServer::_executeTool(const JSONRPCCall& call) { - String toolName; + TestServerProtocol::ExecuteToolTestArgs args; + + SLANG_RETURN_ON_FAIL(m_connection->toNativeOrSendError(call.params, &args)); + + auto sink = m_connection->getSink(); - auto func = getToolFunction(toolName, &m_diagnosticSink); + auto func = getToolFunction(args.toolName, sink); if (!func) { - // Write out to diagnostics - return SLANG_FAIL; + return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams); } // Assume we will used the shared session - slang::IGlobalSession* session = getGlobalSession(); + slang::IGlobalSession* session = getOrCreateGlobalSession(); if (!session) { return SLANG_FAIL; } - // Get the args list - // Work out the args sent to the shared library - List<const char*> args; + List<const char*> toolArgs; - - RefPtr<StdWriters> stdWriters = StdWriters::createDefault(); + // Add the 'exe' name + toolArgs.add(args.toolName.getBuffer()); - const SlangResult res = func(stdWriters, session, int(args.getCount()), args.begin()); - if (SLANG_FAILED(res)) + // Add the args + for (const auto& arg : args.args) { - return res; + toolArgs.add(arg.getBuffer()); } - return res; -} + StdWriters stdWriters; + StringBuilder stdOut; + StringBuilder stdError; + + // Make writer/s act as if they are the console. + RefPtr<StringWriter> stdOutWriter(new StringWriter(&stdOut, WriterFlag::IsConsole)); + RefPtr<StringWriter> stdErrorWriter(new StringWriter(&stdError, WriterFlag::IsConsole)); + + stdWriters.setWriter(SLANG_WRITER_CHANNEL_STD_ERROR, stdErrorWriter); + stdWriters.setWriter(SLANG_WRITER_CHANNEL_STD_OUTPUT, stdOutWriter); + + // HACK, to make behavior the same as previously + if (args.toolName== "slangc") + { + stdWriters.setWriter(SLANG_WRITER_CHANNEL_DIAGNOSTIC, stdErrorWriter); + } + + const SlangResult funcRes = func(&stdWriters, session, int(toolArgs.getCount()), toolArgs.begin()); + + TestServerProtocol::ExecutionResult result; + result.result = funcRes; + result.stdError = stdError; + result.stdOut = stdOut; + + result.returnCode = int32_t(TestToolUtil::getReturnCode(result.result)); + return m_connection->sendResult(&result); +} SlangResult TestServer::execute() { - DiagnosticSink diagnosticSink; - while (m_connection->isActive() && !m_quit) { - SlangResult res = _executeSingle(); - if (m_quit) - { - break; - } - - if (SLANG_FAILED(res)) - { - // Return a result - } + // Failure doesn't make the execution terminate + const SlangResult res = _executeSingle(); } return SLANG_OK; |
