summaryrefslogtreecommitdiffstats
path: root/tools/test-server/test-server-main.cpp
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2021-11-23 16:23:15 -0500
committerGitHub <noreply@github.com>2021-11-23 16:23:15 -0500
commit9e084ffab37c276d40931a58633041a2e10de623 (patch)
treed85aa490968cdd3fe4bbcf305b593c6b86587685 /tools/test-server/test-server-main.cpp
parentfd46034bf2de59b8ad51743e62b26359678432f7 (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 'tools/test-server/test-server-main.cpp')
-rw-r--r--tools/test-server/test-server-main.cpp337
1 files changed, 200 insertions, 137 deletions
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;