yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
374026df1
master
1// unit-test-record-replay.cpp 2 3#include "../../source/core/slang-http.h" 4#include "../../source/core/slang-io.h" 5#include "../../source/core/slang-process-util.h" 6#include "../../source/core/slang-random-generator.h" 7#include "../../source/core/slang-string-util.h" 8#include "unit-test/slang-unit-test.h" 9 10#include <chrono> 11#include <thread> 12 13using namespace Slang ; 14 15static SlangResult createProcess ( 16UnitTestContext * context , 17const char * processName , 18const List < String >* optArgs , 19RefPtr < Process >& outProcess ) 20{ 21CommandLine cmdLine ; 22cmdLine .setExecutableLocation (ExecutableLocation (context -> executableDirectory ,processName )); 23if (optArgs ) 24 { 25cmdLine .m_args .addRange (optArgs -> getBuffer (),optArgs -> getCount ()); 26 } 27 28SLANG_RETURN_ON_FAIL (Process ::create (cmdLine ,Process ::Flag ::AttachDebugger ,outProcess )); 29 30return SLANG_OK ; 31} 32 33struct entryHashInfo 34{ 35int64_t callIdx = -1 ; 36int64_t targetIndex = -1 ; 37int64_t entryPointIndex = -1 ; 38String hash ; 39}; 40 41static SlangResult parseHashes (List < String > const & lines ,List < entryHashInfo >& outHashes ) 42{ 43SlangResult res = SLANG_OK ; 44 45for (const auto & line :lines ) 46 { 47List < UnownedStringSlice > tokens ; 48Index skipCharacters = line .indexOf (UnownedStringSlice ("[slang-record-replay]:" )); 49if (skipCharacters == -1 ) 50 { 51skipCharacters = 0 ; 52 } 53else 54 { 55skipCharacters += strlen ("[slang-record-replay]:" ); 56 } 57StringUtil ::split (UnownedStringSlice (line .getBuffer ()+ skipCharacters ),',' ,tokens ); 58 59if (tokens .getCount ()!= 4 ) 60 { 61return SLANG_FAIL ; 62 } 63 64entryHashInfo hashInfo ; 65auto extractToken = [](const UnownedStringSlice & token , 66const char splitChar , 67UnownedStringSlice & outToken )-> SlangResult 68 { 69List < UnownedStringSlice > subTokens ; 70StringUtil ::split (token ,splitChar ,subTokens ); 71if (subTokens .getCount ()!= 2 ) 72 { 73return SLANG_FAIL ; 74 } 75outToken = subTokens [1 ]; 76return SLANG_OK ; 77 }; 78 79 { 80UnownedStringSlice subToken ; 81SLANG_RETURN_ON_FAIL (extractToken (tokens [0 ],':' ,subToken )); 82int64_t outNumer = 0 ; 83StringUtil ::parseInt64 (subToken ,outNumer ); 84hashInfo .callIdx = outNumer ; 85 } 86 87 { 88UnownedStringSlice subToken ; 89SLANG_RETURN_ON_FAIL (extractToken (tokens [1 ],':' ,subToken )); 90int64_t outNumer = 0 ; 91StringUtil ::parseInt64 (subToken ,outNumer ); 92hashInfo .entryPointIndex = outNumer ; 93 } 94 95 { 96UnownedStringSlice subToken ; 97SLANG_RETURN_ON_FAIL (extractToken (tokens [2 ],':' ,subToken )); 98int64_t outNumer = 0 ; 99StringUtil ::parseInt64 (subToken ,outNumer ); 100hashInfo .targetIndex = outNumer ; 101 } 102 103 { 104UnownedStringSlice subToken ; 105SLANG_RETURN_ON_FAIL (extractToken (tokens [3 ],':' ,subToken )); 106// remove the white space after ":" 107hashInfo .hash = subToken .begin ()+ 1 ; 108 } 109 110outHashes .add (hashInfo ); 111 } 112return res ; 113} 114 115static int writeEnvironmentVariable (const char * key ,const char * val ) 116{ 117#ifdef _WIN32 118String var = String (key )+ "=" + val ; 119return _putenv (var .getBuffer ()); 120#else 121return setenv (key ,val ,1 ); 122#endif 123} 124 125static bool enableRecordLayer () 126{ 127int retCode = writeEnvironmentVariable ("SLANG_RECORD_LAYER" ,"1" ); 128return retCode == 0 ; 129} 130 131static bool disableRecordLayer () 132{ 133int retCode = writeEnvironmentVariable ("SLANG_RECORD_LAYER" ,"0" ); 134return retCode == 0 ; 135} 136 137static bool enableLogInReplayer () 138{ 139int retCode = writeEnvironmentVariable ("SLANG_RECORD_LOG_LEVEL" ,"3" ); 140return retCode == 0 ; 141} 142 143static bool disableLogInReplayer () 144{ 145int retCode = writeEnvironmentVariable ("SLANG_RECORD_LOG_LEVEL" ,"0" ); 146return retCode == 0 ; 147} 148 149static void findRecordFileName (List < String >* fileNames ,const String & recordDir ) 150{ 151struct Visitor :Path ::Visitor 152 { 153void accept (Path ::Type type ,const UnownedStringSlice & filename )SLANG_OVERRIDE 154 { 155if (type == Path ::Type ::File ) 156 { 157m_fileNames -> add (filename ); 158 } 159 } 160Visitor (List < String >* fileNames ) 161 :m_fileNames (fileNames ) 162 { 163 } 164List < String >* m_fileNames ; 165 }; 166 167Visitor visitor (fileNames ); 168Path ::find (recordDir .getBuffer (),"*.cap" ,& visitor ); 169} 170 171static SlangResult launchProcessAndReadStdout ( 172UnitTestContext * context , 173const List < String >& optArgs , 174const char * exampleName , 175RefPtr < Process >& process , 176ExecuteResult & exeRes ) 177{ 178StringBuilder msgBuilder ; 179SlangResult res = createProcess (context ,exampleName ,& optArgs ,process ); 180if (SLANG_FAILED (res )) 181 { 182msgBuilder <<"Failed to launch process of '" <<exampleName <<"'\n" ; 183getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 184return res ; 185 } 186 187res = ProcessUtil ::readUntilTermination (process ,exeRes ); 188if (SLANG_FAILED (res )) 189 { 190msgBuilder <<"Failed to read stdout from '" <<exampleName <<"'\n" ; 191msgBuilder <<"process ret code: " <<exeRes .resultCode ; 192getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 193return res ; 194 } 195 196if (exeRes .resultCode != 0 ) 197 { 198msgBuilder <<"'" <<exampleName <<"' exits with failure\n" ; 199msgBuilder <<"Process ret code: " <<exeRes .resultCode <<"\n" ; 200msgBuilder <<"Standard output:\n" <<exeRes .standardOutput ; 201msgBuilder <<"Standard error:\n" <<exeRes .standardError ; 202getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 203return SLANG_FAIL ; 204 } 205 206if (exeRes .standardOutput .getLength ()== 0 ) 207 { 208msgBuilder <<"No stdout found in '" <<exampleName <<"'\n" ; 209msgBuilder <<"Standard error: " <<exeRes .standardError ; 210getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 211return SLANG_FAIL ; 212 } 213return SLANG_OK ; 214} 215 216static SlangResult runExample ( 217UnitTestContext * context , 218const char * exampleName , 219const String & recordDir , 220List < entryHashInfo >& outHashes ) 221{ 222SlangResult finalRes = SLANG_OK ; 223 224RefPtr < Process > process ; 225ExecuteResult exeRes ; 226List < String > optArgs ; 227optArgs .add ("--test-mode" ); 228 229StringBuilder msgBuilder ; 230SlangResult res = SLANG_OK ; 231 232// Set unique record directory for this test 233writeEnvironmentVariable ("SLANG_RECORD_DIRECTORY" ,recordDir .getBuffer ()); 234enableRecordLayer (); 235res = launchProcessAndReadStdout (context ,optArgs ,exampleName ,process ,exeRes ); 236disableRecordLayer (); 237 238if (SLANG_FAILED (res )) 239 { 240return res ; 241 } 242 243List < String > hashLines ; 244for (auto line :LineParser (exeRes .standardOutput .getUnownedSlice ())) 245 { 246if (line .getLength ()== 0 ) 247 { 248continue ; 249 } 250 251if (line .indexOf (UnownedStringSlice ("hash:" ))== -1 ) 252 { 253continue ; 254 } 255 256hashLines .add (line ); 257 } 258 259if (hashLines .getCount ()== 0 ) 260 { 261msgBuilder <<"Hash value is not found for '" <<exampleName <<"'\n" ; 262msgBuilder <<"Process ret code: " <<exeRes .resultCode <<"\n" ; 263msgBuilder <<"Standard output:\n" <<exeRes .standardOutput <<"\n" ; 264msgBuilder <<"Standard error:\n" <<exeRes .standardError <<"\n" ; 265getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 266return SLANG_FAIL ; 267 } 268 269res = parseHashes (hashLines ,outHashes ); 270if (SLANG_FAILED (res )) 271 { 272msgBuilder <<"Failed to parse hash from stdout of '" <<exampleName <<"'\n" ; 273getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 274return res ; 275 } 276 277return SLANG_OK ; 278} 279 280static SlangResult replayExample ( 281UnitTestContext * context , 282const String & recordDir , 283List < entryHashInfo >& outHashes ) 284{ 285List < String > fileNames ; 286findRecordFileName (& fileNames ,recordDir ); 287if (fileNames .getCount ()== 0 ) 288 { 289getTestReporter ()-> message (TestMessageType ::TestFailure ,"No record files found\n" ); 290return SLANG_FAIL ; 291 } 292 293List < String > optArgs ; 294String recordFileName = Path ::combine (recordDir ,fileNames [0 ]); 295optArgs .add (recordFileName .getBuffer ()); 296 297RefPtr < Process > process ; 298ExecuteResult exeRes ; 299 300StringBuilder msgBuilder ; 301msgBuilder <<"replay the test\n" ; 302 303enableLogInReplayer (); 304SlangResult res = launchProcessAndReadStdout (context ,optArgs ,"slang-replay" ,process ,exeRes ); 305disableLogInReplayer (); 306 307if (SLANG_FAILED (res )) 308 { 309return res ; 310 } 311 312List < String > hashLines ; 313for (auto line :LineParser (exeRes .standardOutput .getUnownedSlice ())) 314 { 315if (line .getLength ()== 0 ) 316 { 317continue ; 318 } 319 320if (line .indexOf (UnownedStringSlice ("hash:" ))== -1 ) 321 { 322continue ; 323 } 324 325hashLines .add (line ); 326 } 327 328res = parseHashes (hashLines ,outHashes ); 329if (SLANG_FAILED (res )) 330 { 331msgBuilder <<"Failed to parse hash from stdout of 'slang-replay'\n" ; 332getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 333return SLANG_FAIL ; 334 } 335 336return SLANG_OK ; 337} 338 339static SlangResult resultCompare ( 340List < entryHashInfo > const & expectHashes , 341List < entryHashInfo > const & resultHashes ) 342{ 343if (expectHashes .getCount ()== 0 ) 344 { 345getTestReporter ()-> message (TestMessageType ::TestFailure ,"No hash found\n" ); 346return SLANG_FAIL ; 347 } 348 349StringBuilder msgBuilder ; 350if (expectHashes .getCount ()!= resultHashes .getCount ()) 351 { 352msgBuilder <<"The number of hashes doesn't match, expect: " <<expectHashes .getCount () 353 <<", actual: " <<resultHashes .getCount () <<"\n" ; 354getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 355return SLANG_FAIL ; 356 } 357 358for (Index i = 0 ;i < expectHashes .getCount ();i ++ ) 359 { 360if (expectHashes [i ].targetIndex != resultHashes [i ].targetIndex ) 361 { 362msgBuilder <<"Failed to match 'targetIndex' at index " <<i <<"\n" ; 363msgBuilder <<"Expect: " <<expectHashes [i ].targetIndex 364 <<", actual: " <<resultHashes [i ].targetIndex <<"\n" ; 365getTestReporter ()-> message ( 366TestMessageType ::TestFailure , 367msgBuilder .toString ().getBuffer ()); 368return SLANG_FAIL ; 369 } 370if (expectHashes [i ].entryPointIndex != resultHashes [i ].entryPointIndex ) 371 { 372msgBuilder <<"Failed to match 'entryPointIndex' at index " <<i <<"\n" ; 373msgBuilder <<"Expect: " <<expectHashes [i ].entryPointIndex 374 <<", actual: " <<resultHashes [i ].entryPointIndex <<"\n" ; 375getTestReporter ()-> message ( 376TestMessageType ::TestFailure , 377msgBuilder .toString ().getBuffer ()); 378return SLANG_FAIL ; 379 } 380 381if (expectHashes [i ].hash != resultHashes [i ].hash ) 382 { 383msgBuilder <<"Failed to match 'hash' at index " <<i <<"\n" ; 384msgBuilder <<"Expect: " <<expectHashes [i ].hash <<", actual: " <<resultHashes [i ].hash 385 <<"\n" ; 386getTestReporter ()-> message ( 387TestMessageType ::TestFailure , 388msgBuilder .toString ().getBuffer ()); 389return SLANG_FAIL ; 390 } 391 } 392 393return SLANG_OK ; 394} 395 396static SlangResult cleanupRecordFiles (const String & recordDir ) 397{ 398SlangResult res = Path ::removeNonEmpty (recordDir .getBuffer ()); 399if (SLANG_FAILED (res )) 400 { 401StringBuilder msgBuilder ; 402msgBuilder <<"Failed to remove '" <<recordDir <<"' directory\n" ; 403getTestReporter ()-> message (TestMessageType ::TestFailure ,msgBuilder .toString ().getBuffer ()); 404 } 405 406return res ; 407} 408 409static SlangResult runTest (UnitTestContext * context ,const char * testName ) 410{ 411// Create unique directory for this test to avoid conflicts 412StringBuilder recordDirBuilder ; 413recordDirBuilder <<"slang-record-" <<testName ; 414String recordDir = recordDirBuilder .toString (); 415 416List < entryHashInfo > expectHashes ; 417List < entryHashInfo > resultHashes ; 418SlangResult res = SLANG_OK ; 419 420// Run the example to generate recording 421res = runExample (context ,testName ,recordDir ,expectHashes ); 422if (SLANG_SUCCEEDED (res )) 423 { 424// Replay the recording 425res = replayExample (context ,recordDir ,resultHashes ); 426if (SLANG_SUCCEEDED (res )) 427 { 428// Compare results 429res = resultCompare (expectHashes ,resultHashes ); 430 } 431 } 432 433// Always cleanup, regardless of success or failure 434cleanupRecordFiles (recordDir ); 435return res ; 436} 437 438// Those examples all depend on the Vulkan, so we only run them on non-Apple platforms. 439// In the future, we may be able to modify the examples further to remove all the render APIs 440// such that it can be ran on Apple platforms. 441#if !(SLANG_APPLE_FAMILY ) 442 443SLANG_UNIT_TEST (RecordReplay_cpu_hello_world ) 444{ 445SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"cpu-hello-world" ))); 446} 447 448SLANG_UNIT_TEST (RecordReplay_triangle ) 449{ 450SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"triangle" ))); 451} 452 453SLANG_UNIT_TEST (RecordReplay_ray_tracing ) 454{ 455SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"ray-tracing" ))); 456} 457 458// This causes a Windows Graphics driver crash. 459// Temporarily disabled; issue #8022 460#if 0 461SLANG_UNIT_TEST (RecordReplay_ray_tracing_pipeline ) 462{ 463SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"ray-tracing-pipeline" ))); 464} 465#endif 466 467SLANG_UNIT_TEST (RecordReplay_autodiff_texture ) 468{ 469SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"autodiff-texture" ))); 470} 471 472SLANG_UNIT_TEST (RecordReplay_gpu_printing ) 473{ 474SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"gpu-printing" ))); 475} 476 477#if 0 478// These examples requires reflection API to replay, we have to disable 479// it for now. "model-viewer", 480 481SLANG_UNIT_TEST (RecordReplay_shader_object ) 482{ 483SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"shader-object" ))); 484} 485 486SLANG_UNIT_TEST (RecordReplay_model_viewer ) 487{ 488SLANG_CHECK (SLANG_SUCCEEDED (runTest (unitTestContext ,"model-viewer" ))); 489} 490#endif 491 492#endif