yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Jay KwakAdding slang-test option to ignore abort popup message (#8492)979e16a34

master
18.1 KiB641 linesraw
1// test-server.cpp
2
3#include "../../source/compiler-core/slang-json-rpc-connection.h"
4#include "../../source/compiler-core/slang-test-server-protocol.h"
5#include "../../source/core/slang-io.h"
6#include "../../source/core/slang-process-util.h"
7#include "../../source/core/slang-secure-crt.h"
8#include "../../source/core/slang-shared-library.h"
9#include "../../source/core/slang-string-util.h"
10#include "../../source/core/slang-string.h"
11#include "../../source/core/slang-test-tool-util.h"
12#include "../../source/core/slang-writer.h"
13#include "../render-test/slang-support.h"
14#include "gfx-unit-test/gfx-test-util.h"
15#include "slang-com-helper.h"
16#include "slang-rhi.h"
17#include "test-server-diagnostics.h"
18#include "unit-test/slang-unit-test.h"
19
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23
24#if defined(_WIN32)
25#include <slang-rhi/agility-sdk.h>
26SLANG_RHI_EXPORT_AGILITY_SDK
27#endif
28
29namespace TestServer
30{
31using namespace Slang;
32
33class TestReporter : public ITestReporter
34{
35public:
36    // ITestReporter
37    virtual SLANG_NO_THROW void SLANG_MCALL startTest(const char* testName) SLANG_OVERRIDE {}
38    virtual SLANG_NO_THROW void SLANG_MCALL addResult(TestResult result) SLANG_OVERRIDE;
39    virtual SLANG_NO_THROW void SLANG_MCALL
40    addResultWithLocation(TestResult result, const char* testText, const char* file, int line)
41        SLANG_OVERRIDE;
42    virtual SLANG_NO_THROW void SLANG_MCALL
43    addResultWithLocation(bool testSucceeded, const char* testText, const char* file, int line)
44        SLANG_OVERRIDE;
45    virtual SLANG_NO_THROW void SLANG_MCALL addExecutionTime(double time) SLANG_OVERRIDE {}
46    virtual SLANG_NO_THROW void SLANG_MCALL message(TestMessageType type, const char* message)
47        SLANG_OVERRIDE;
48    virtual SLANG_NO_THROW void SLANG_MCALL endTest() SLANG_OVERRIDE {}
49
50    StringBuilder m_buf;
51    Index m_failCount = 0;
52    Index m_testCount = 0;
53};
54
55class TestServer
56{
57public:
58    typedef Slang::TestToolUtil::InnerMainFunc InnerMainFunc;
59
60    SlangResult init(int argc, const char* const* argv);
61
62    /// Can return nullptr if cannot create the session
63    slang::IGlobalSession* getOrCreateGlobalSession();
64
65    /// Can return nullptr if cannot load the tool
66    ISlangSharedLibrary* loadSharedLibrary(const String& name, DiagnosticSink* sink = nullptr);
67
68    /// Get a unit test module. Returns nullptr if not found.
69    IUnitTestModule* getUnitTestModule(const String& name, DiagnosticSink* sink = nullptr);
70
71    /// Given a tool name return it's function pointer. Or nullptr on failure.
72    InnerMainFunc getToolFunction(const String& name, DiagnosticSink* sink = nullptr);
73
74    /// Execute the server
75    SlangResult execute();
76
77    /// Dtor
78    ~TestServer();
79
80protected:
81    SlangResult _executeSingle();
82    SlangResult _executeUnitTest(const JSONRPCCall& call);
83    SlangResult _executeTool(const JSONRPCCall& root);
84
85    bool m_quit = false;
86
87    ComPtr<slang::IGlobalSession> m_session; /// The slang session. Is created on demand
88
89    Dictionary<String, ComPtr<ISlangSharedLibrary>>
90        m_sharedLibraryMap;                                 ///< Maps tool names to the dll
91    Dictionary<String, IUnitTestModule*> m_unitTestModules; ///< All the unit test modules.
92
93    String m_exePath;      ///< Path to executable (including exe name)
94    String m_exeDirectory; ///< The directory that holds the exe
95
96    RefPtr<JSONRPCConnection> m_connection; ///< RPC connection, recieves calls to execute and
97                                            ///< returns results via JSON-RPC
98};
99
100/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! TestServer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
101
102namespace SlangCTool
103{
104
105static void _diagnosticCallback(char const* message, void* userData)
106{
107    ISlangWriter* writer = (ISlangWriter*)userData;
108    writer->write(message, strlen(message));
109}
110
111SlangResult innerMain(
112    StdWriters* stdWriters,
113    slang::IGlobalSession* sharedSession,
114    int argc,
115    const char* const* argv)
116{
117    // Assume we will used the shared session
118    ComPtr<slang::IGlobalSession> session(sharedSession);
119
120    // The sharedSession always has a pre-loaded core module.
121    // This differed test checks if the command line has an option to setup the core module.
122    // If so we *don't* use the sharedSession, and create a new session without the core module just
123    // for this compilation.
124    if (TestToolUtil::hasDeferredCoreModule(Index(argc - 1), argv + 1))
125    {
126        SLANG_RETURN_ON_FAIL(
127            slang_createGlobalSessionWithoutCoreModule(SLANG_API_VERSION, session.writeRef()));
128    }
129
130    ComPtr<slang::ICompileRequest> compileRequest;
131    SLANG_ALLOW_DEPRECATED_BEGIN
132    SLANG_RETURN_ON_FAIL(session->createCompileRequest(compileRequest.writeRef()));
133    SLANG_ALLOW_DEPRECATED_END
134
135    // Do any app specific configuration
136    for (int i = 0; i < int{SLANG_WRITER_CHANNEL_COUNT_OF}; ++i)
137    {
138        const auto channel = SlangWriterChannel(i);
139        compileRequest->setWriter(channel, stdWriters->getWriter(channel));
140    }
141
142    compileRequest->setDiagnosticCallback(
143        &_diagnosticCallback,
144        stdWriters->getWriter(SLANG_WRITER_CHANNEL_STD_ERROR));
145    compileRequest->setCommandLineCompilerMode();
146
147    {
148        const SlangResult res = compileRequest->processCommandLineArguments(&argv[1], argc - 1);
149        if (SLANG_FAILED(res))
150        {
151            // TODO: print usage message
152            return res;
153        }
154    }
155
156    SlangResult compileRes = SLANG_OK;
157
158#ifndef _DEBUG
159    try
160#endif
161    {
162        // Run the compiler (this will produce any diagnostics through
163        // SLANG_WRITER_TARGET_TYPE_DIAGNOSTIC).
164        compileRes = compileRequest->compile();
165
166        // If the compilation failed, then get out of here...
167        // Turn into an internal Result -> such that return code can be used to vary result to match
168        // previous behavior
169        compileRes = SLANG_FAILED(compileRes) ? SLANG_E_INTERNAL_FAIL : compileRes;
170    }
171#ifndef _DEBUG
172    catch (const Exception& e)
173    {
174        WriterHelper writerHelper(stdWriters->getWriter(SLANG_WRITER_CHANNEL_STD_OUTPUT));
175        writerHelper.print("internal compiler error: %S\n", e.Message.toWString().begin());
176        compileRes = SLANG_FAIL;
177    }
178#endif
179
180    return compileRes;
181}
182
183} // namespace SlangCTool
184
185// SlangITool
186#include "../slang-test/slangi-tool-impl.h"
187
188/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! TestServer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
189
190SlangResult TestServer::init(int argc, const char* const* argv)
191{
192    m_exePath = argv[0];
193
194    // Command-line argument parsing
195    for (int i = 1; i < argc; i++)
196    {
197        if (strcmp(argv[i], "-ignore-abort-msg") == 0)
198        {
199#ifdef _MSC_VER
200            _set_abort_behavior(0, _WRITE_ABORT_MSG);
201#endif
202        }
203        // Ignore unknown arguments for now
204    }
205
206    String canonicalPath;
207    if (SLANG_SUCCEEDED(Path::getCanonical(m_exePath, canonicalPath)))
208    {
209        m_exeDirectory = Path::getParentDirectory(canonicalPath);
210    }
211    else
212    {
213        m_exeDirectory = Path::getParentDirectory(m_exePath);
214    }
215
216    m_connection = new JSONRPCConnection;
217    SLANG_RETURN_ON_FAIL(m_connection->initWithStdStreams());
218    return SLANG_OK;
219}
220
221TestServer::~TestServer()
222{
223    for (auto& [_, value] : m_unitTestModules)
224        value->destroy();
225}
226
227slang::IGlobalSession* TestServer::getOrCreateGlobalSession()
228{
229    if (!m_session)
230    {
231        // Just create the global session in the regular way if there isn't one set
232        SlangGlobalSessionDesc desc = {};
233        desc.enableGLSL = true;
234        if (SLANG_FAILED(slang_createGlobalSession2(&desc, m_session.writeRef())))
235        {
236            return nullptr;
237        }
238        TestToolUtil::setSessionDefaultPreludeFromExePath(m_exePath.getBuffer(), m_session);
239    }
240
241    return m_session;
242}
243
244ISlangSharedLibrary* TestServer::loadSharedLibrary(const String& name, DiagnosticSink* sink)
245{
246    ComPtr<ISlangSharedLibrary> lib;
247    if (m_sharedLibraryMap.tryGetValue(name, lib))
248    {
249        return lib;
250    }
251
252    auto loader = DefaultSharedLibraryLoader::getSingleton();
253
254    ComPtr<ISlangSharedLibrary> sharedLibrary;
255    if (SLANG_FAILED(loader->loadSharedLibrary(name.getBuffer(), sharedLibrary.writeRef())))
256    {
257        if (sink)
258        {
259            sink->diagnose(SourceLoc(), ServerDiagnostics::unableToLoadSharedLibrary, name);
260        }
261
262        return nullptr;
263    }
264
265    m_sharedLibraryMap.add(name, sharedLibrary);
266    return sharedLibrary;
267}
268
269IUnitTestModule* TestServer::getUnitTestModule(const String& name, DiagnosticSink* sink)
270{
271    auto unitTestModulePtr = m_unitTestModules.tryGetValue(name);
272    if (unitTestModulePtr)
273    {
274        return *unitTestModulePtr;
275    }
276
277    ISlangSharedLibrary* sharedLibrary = loadSharedLibrary(name, sink);
278    if (!sharedLibrary)
279    {
280        return nullptr;
281    }
282
283    const char funcName[] = "slangUnitTestGetModule";
284
285    // get the unit test export name
286    UnitTestGetModuleFunc getModuleFunc =
287        (UnitTestGetModuleFunc)sharedLibrary->findFuncByName(funcName);
288    if (!getModuleFunc)
289    {
290        if (sink)
291        {
292            sink->diagnose(
293                SourceLoc(),
294                ServerDiagnostics::unableToFindFunctionInSharedLibrary,
295                funcName);
296        }
297        return nullptr;
298    }
299
300    IUnitTestModule* testModule = getModuleFunc();
301    if (!testModule)
302    {
303        if (sink)
304        {
305            sink->diagnose(SourceLoc(), ServerDiagnostics::unableToGetUnitTestModule);
306        }
307        return nullptr;
308    }
309
310    m_unitTestModules.add(name, testModule);
311    return testModule;
312}
313
314TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, DiagnosticSink* sink)
315{
316    if (name == "slangc")
317    {
318        return &SlangCTool::innerMain;
319    }
320    else if (name == "slangi")
321    {
322        return &SlangITool::innerMain;
323    }
324
325    StringBuilder sharedLibToolBuilder;
326    sharedLibToolBuilder.append(name);
327    sharedLibToolBuilder.append("-tool");
328
329    ISlangSharedLibrary* sharedLibrary = loadSharedLibrary(sharedLibToolBuilder, sink);
330    if (!sharedLibrary)
331    {
332        return nullptr;
333    }
334
335    const char funcName[] = "innerMain";
336
337    auto func = (InnerMainFunc)sharedLibrary->findFuncByName(funcName);
338    if (!func && sink)
339    {
340        sink->diagnose(
341            SourceLoc(),
342            ServerDiagnostics::unableToFindFunctionInSharedLibrary,
343            funcName);
344    }
345
346    return func;
347}
348
349SlangResult TestServer::_executeSingle()
350{
351    // Block waiting for content (or error/closed)
352    SLANG_RETURN_ON_FAIL(m_connection->waitForResult());
353
354    // If we don't have a message, we can quit for now
355    if (!m_connection->hasMessage())
356    {
357        return SLANG_OK;
358    }
359
360    const JSONRPCMessageType msgType = m_connection->getMessageType();
361
362    switch (msgType)
363    {
364    case JSONRPCMessageType::Call:
365        {
366            JSONRPCCall call;
367            SLANG_RETURN_ON_FAIL(m_connection->getRPCOrSendError(&call));
368
369            // Do different things
370            if (call.method == TestServerProtocol::QuitArgs::g_methodName)
371            {
372                m_quit = true;
373                return SLANG_OK;
374            }
375            else if (call.method == TestServerProtocol::ExecuteUnitTestArgs::g_methodName)
376            {
377                SLANG_RETURN_ON_FAIL(_executeUnitTest(call));
378                return SLANG_OK;
379            }
380            else if (call.method == TestServerProtocol::ExecuteToolTestArgs::g_methodName)
381            {
382                SLANG_RETURN_ON_FAIL(_executeTool(call));
383                break;
384            }
385            else
386            {
387                return m_connection->sendError(JSONRPC::ErrorCode::MethodNotFound, call.id);
388            }
389        }
390    default:
391        {
392            return m_connection->sendError(
393                JSONRPC::ErrorCode::InvalidRequest,
394                m_connection->getCurrentMessageId());
395        }
396    }
397
398    return SLANG_OK;
399}
400
401static Index _findTestIndex(IUnitTestModule* testModule, const String& name)
402{
403    const auto testCount = testModule->getTestCount();
404    for (SlangInt i = 0; i < testCount; ++i)
405    {
406        auto testName = testModule->getTestName(i);
407
408        if (name == testName)
409        {
410            return Index(i);
411        }
412    }
413    return -1;
414}
415
416SlangResult TestServer::_executeUnitTest(const JSONRPCCall& call)
417{
418    auto id = m_connection->getPersistentValue(call.id);
419
420    TestServerProtocol::ExecuteUnitTestArgs args;
421    SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, call.id));
422
423    auto sink = m_connection->getSink();
424
425    IUnitTestModule* testModule = getUnitTestModule(args.moduleName, m_connection->getSink());
426    if (!testModule)
427    {
428        sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindUnitTestModule, args.moduleName);
429        return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams, id);
430    }
431
432    const Index testIndex = _findTestIndex(testModule, args.testName);
433    if (testIndex < 0)
434    {
435        sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindTest, args.testName);
436        return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams, id);
437    }
438
439    TestReporter testReporter;
440    renderer_test::CoreDebugCallback coreDebugCallback;
441    renderer_test::CoreToRHIDebugBridge rhiDebugCallback;
442    rhiDebugCallback.setCoreCallback(&coreDebugCallback);
443
444    testModule->setTestReporter(&testReporter);
445
446    // Assume we will used the shared session
447    slang::IGlobalSession* session = getOrCreateGlobalSession();
448    if (!session)
449    {
450        return SLANG_FAIL;
451    }
452
453    UnitTestContext unitTestContext;
454    unitTestContext.slangGlobalSession = session;
455    unitTestContext.workDirectory = "";
456    unitTestContext.enabledApis = RenderApiFlags(args.enabledApis);
457    unitTestContext.executableDirectory = m_exeDirectory.getBuffer();
458    unitTestContext.enableDebugLayers = args.enableDebugLayers;
459    unitTestContext.debugCallback = &rhiDebugCallback;
460
461    auto testCount = testModule->getTestCount();
462    SLANG_ASSERT(testIndex >= 0 && testIndex < testCount);
463
464    UnitTestFunc testFunc = testModule->getTestFunc(testIndex);
465
466    try
467    {
468        testFunc(&unitTestContext);
469    }
470    catch (...)
471    {
472        testReporter.m_failCount++;
473    }
474
475    TestServerProtocol::ExecutionResult result;
476    result.result = SLANG_OK;
477    result.debugLayer = coreDebugCallback.getString();
478
479    if (testReporter.m_failCount > 0)
480    {
481        result.result = SLANG_FAIL;
482        result.stdError = testReporter.m_buf.getUnownedSlice();
483    }
484    else if (testReporter.m_testCount == 0)
485    {
486        result.result = SLANG_E_NOT_AVAILABLE;
487    }
488
489    result.returnCode = int32_t(TestToolUtil::getReturnCode(result.result));
490    return m_connection->sendResult(&result, id);
491}
492
493SlangResult TestServer::_executeTool(const JSONRPCCall& call)
494{
495    auto id = m_connection->getPersistentValue(call.id);
496
497    TestServerProtocol::ExecuteToolTestArgs args;
498
499    SLANG_RETURN_ON_FAIL(m_connection->toNativeArgsOrSendError(call.params, &args, id));
500
501    auto sink = m_connection->getSink();
502
503    auto func = getToolFunction(args.toolName, sink);
504    if (!func)
505    {
506        return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams, id);
507    }
508
509    // Assume we will used the shared session
510    slang::IGlobalSession* session = getOrCreateGlobalSession();
511    if (!session)
512    {
513        return SLANG_FAIL;
514    }
515
516    // Work out the args sent to the shared library
517    List<const char*> toolArgs;
518
519    // Add the 'exe' name
520    toolArgs.add(args.toolName.getBuffer());
521
522    // Add the args
523    for (const auto& arg : args.args)
524    {
525        toolArgs.add(arg.getBuffer());
526    }
527
528    StdWriters stdWriters;
529    StringBuilder stdOut;
530    StringBuilder stdError;
531    renderer_test::CoreDebugCallback debugCallback;
532
533    // Make writer/s act as if they are the console.
534    RefPtr<StringWriter> stdOutWriter(new StringWriter(&stdOut, WriterFlag::IsConsole));
535    RefPtr<StringWriter> stdErrorWriter(new StringWriter(&stdError, WriterFlag::IsConsole));
536
537    stdWriters.setWriter(SLANG_WRITER_CHANNEL_STD_ERROR, stdErrorWriter);
538    stdWriters.setWriter(SLANG_WRITER_CHANNEL_STD_OUTPUT, stdOutWriter);
539    stdWriters.setDebugCallback(&debugCallback);
540
541    // HACK, to make behavior the same as previously
542    if (args.toolName == "slangc")
543    {
544        stdWriters.setWriter(SLANG_WRITER_CHANNEL_DIAGNOSTIC, stdErrorWriter);
545    }
546
547    const SlangResult funcRes =
548        func(&stdWriters, session, int(toolArgs.getCount()), toolArgs.begin());
549
550    TestServerProtocol::ExecutionResult result;
551    result.result = funcRes;
552    result.stdError = stdError;
553    result.stdOut = stdOut;
554    result.debugLayer = debugCallback.getString();
555
556    result.returnCode = int32_t(TestToolUtil::getReturnCode(result.result));
557    return m_connection->sendResult(&result, id);
558}
559
560SlangResult TestServer::execute()
561{
562    while (m_connection->isActive() && !m_quit)
563    {
564        // Failure doesn't make the execution terminate
565        [[maybe_unused]] const SlangResult res = _executeSingle();
566    }
567
568    return SLANG_OK;
569}
570
571/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TestReporter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
572
573void TestReporter::message(TestMessageType type, const char* message)
574{
575    if (type == TestMessageType::RunError || type == TestMessageType::TestFailure)
576    {
577        m_failCount++;
578    }
579
580    m_buf << message << "\n";
581}
582
583void TestReporter::addResultWithLocation(
584    TestResult result,
585    const char* testText,
586    const char* file,
587    int line)
588{
589    if (result == TestResult::Fail)
590    {
591        addResultWithLocation(false, testText, file, line);
592    }
593    else
594    {
595        m_testCount++;
596    }
597}
598
599void TestReporter::addResultWithLocation(
600    bool testSucceeded,
601    const char* testText,
602    const char* file,
603    int line)
604{
605    m_testCount++;
606
607    if (testSucceeded)
608    {
609        return;
610    }
611
612    m_buf << "[Failed]: " << testText << "\n";
613    m_buf << file << ":" << line << "\n";
614
615    m_failCount++;
616}
617
618void TestReporter::addResult(TestResult result)
619{
620    if (result == TestResult::Fail)
621    {
622        m_failCount++;
623    }
624}
625
626
627SlangResult _execute(int argc, const char* const* argv)
628{
629    TestServer server;
630    SLANG_RETURN_ON_FAIL(server.init(argc, argv));
631    SLANG_RETURN_ON_FAIL(server.execute());
632    slang::shutdown();
633    return SLANG_OK;
634}
635
636} // namespace TestServer
637
638int main(int argc, const char* const* argv)
639{
640    return (int)Slang::TestToolUtil::getReturnCode(TestServer::_execute(argc, argv));
641}