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