yum-mirror/slang

Making it easier to work with shaders

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

Janne Kiviluoto (NVIDIA)Add deterministic shuffling of tests in directory (#8622)e4d1200cb

master
21.1 KiB613 linesraw
1// options.cpp
2#include "options.h"
3
4#include "../../source/core/slang-io.h"
5#include "../../source/core/slang-string-util.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9
10using namespace Slang;
11
12/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! CategorySet !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
13
14TestCategory* TestCategorySet::add(String const& name, TestCategory* parent)
15{
16    RefPtr<TestCategory> category(new TestCategory);
17    category->name = name;
18    category->parent = parent;
19
20    m_categoryMap.add(name, category);
21    return category;
22}
23
24TestCategory* TestCategorySet::find(String const& name)
25{
26    if (auto category = m_categoryMap.tryGetValue(name))
27    {
28        return category->Ptr();
29    }
30    return nullptr;
31}
32
33TestCategory* TestCategorySet::findOrError(String const& name)
34{
35    TestCategory* category = find(name);
36    if (!category)
37    {
38        StdWriters::getError().print("error: unknown test category name '%s'\n", name.getBuffer());
39    }
40    return category;
41}
42
43/* We need a way to differentiate a subCommand from say a test prefix. Here
44we assume a command is just alpha characters or -, and this would differentiate it from
45typical prefix usage (which is generally a directory). */
46static bool _isSubCommand(const char* arg)
47{
48    for (; *arg; arg++)
49    {
50        const char c = *arg;
51        // A command is just letters
52        if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-'))
53        {
54            return false;
55        }
56    }
57    return true;
58}
59
60/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! Options !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
61
62/* static */ void Options::showHelp(WriterHelper stdOut)
63{
64    stdOut.print(
65        "Usage: slang-test [options] [test-prefix...]\n"
66        "\n"
67        "Options:\n"
68        "  -h, --help                     Show this help message\n"
69        "  -bindir <path>                 Set directory for binaries (default: the path to the "
70        "slang-test executable)\n"
71        "  -test-dir <path>               Set directory for test files (default: tests/)\n"
72        "  -v [level]                     Set verbosity level (verbose, info, failure)\n"
73        "                                 Default: verbose when -v used, info otherwise\n"
74        "  -hide-ignored                  Hide results from ignored tests\n"
75        "  -api-only                      Only run tests that use specified APIs\n"
76        "  -verbose-paths                 Use verbose paths in output\n"
77        "  -category <name>               Only run tests in specified category\n"
78        "  -exclude <name>                Exclude tests in specified category\n"
79        "  -exclude-prefix <prefix>       Exclude tests with specified path prefix\n"
80        "  -api <expr>                    Enable specific APIs (e.g., 'vk+dx12' or '+dx11')\n"
81        "  -synthesizedTestApi <expr>     Set APIs for synthesized tests\n"
82        "  -skip-api-detection            Skip API availability detection\n"
83        "  -server-count <n>              Set number of test servers (default: 1)\n"
84        "  -show-adapter-info             Show detailed adapter information\n"
85        "  -generate-hlsl-baselines       Generate HLSL test baselines\n"
86        "  -skip-reference-image-generation Skip generating reference images for render tests\n"
87        "  -emit-spirv-via-glsl           Emit SPIR-V through GLSL instead of directly\n"
88        "  -expected-failure-list <file>  Specify file containing expected failures\n"
89        "  -use-shared-library            Run tests in-process using shared library\n"
90        "  -use-test-server               Run tests using test server\n"
91        "  -use-fully-isolated-test-server  Run each test in isolated server\n"
92        "  -capability <name>             Compile with the given capability\n"
93        "  -shuffle-tests                 Shuffle tests in directories\n"
94        "  -shuffle-seed <seed>           Set shuffle seed (default: 1)\n"
95
96        // Recent Windows runtime versions started opening a dialog popup window when
97        // `abort()` is called, which breaks the CI workflow and some scripts that
98        // expect a normal termination.
99        // It can be helpful for debugging but we should ignore it for CI.
100        "  -ignore-abort-msg              Ignore abort message dialog popup on Windows\n"
101
102        "  -enable-debug-layers [true|false] Enable or disable Validation Layer for Vulkan\n"
103        "                                 and Debug Device for DX\n"
104        "  -cache-rhi-device [true|false] Enable or disable RHI device caching (default: true)\n"
105#if _DEBUG
106        "  -disable-debug-layers          Disable the debug layers (default enabled in debug "
107        "build)\n"
108#endif
109        "\n"
110        "Output modes:\n"
111        "  -appveyor                      Use AppVeyor output format\n"
112        "  -travis                        Use Travis CI output format\n"
113        "  -teamcity                      Use TeamCity output format\n"
114        "  -xunit                         Use xUnit output format\n"
115        "  -xunit2                        Use xUnit 2 output format\n"
116        "\n"
117        "Test prefixes are used to filter which tests to run. If no prefix is specified,\n"
118        "all tests will be run.\n");
119}
120
121/* static */ Result Options::parse(
122    int argc,
123    char** argv,
124    TestCategorySet* categorySet,
125    Slang::WriterHelper stdOut,
126    Slang::WriterHelper stdError,
127    Options* optionsOut)
128{
129    // Reset the options
130    *optionsOut = Options();
131
132    List<const char*> positionalArgs;
133
134    int argCount = argc;
135    char const* const* argCursor = argv;
136    char const* const* argEnd = argCursor + argCount;
137
138#if _DEBUG
139    // Enabling debug layers by default in debug builds.
140    // For DX12 it will use the debug layer, for Vulkan it will enable validation layers.
141    //
142    // CI/CD will explicitly disable this until we address all of VUID errors.
143    // https://github.com/shader-slang/slang/issues/4798
144    //
145    // When you run the Debug build locally, you may see more errors if not disabled with
146    // '-enable-debug-layers false'.
147    //
148    optionsOut->enableDebugLayers = true;
149#endif
150
151    // first argument is the application name
152    if (argCursor != argEnd)
153    {
154        optionsOut->appName = *argCursor++;
155    }
156
157    // Check for help flags first
158    for (int i = 1; i < argc; i++)
159    {
160        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
161        {
162            showHelp(stdOut);
163            return SLANG_FAIL;
164        }
165    }
166
167    // now iterate over arguments to collect options
168    while (argCursor != argEnd)
169    {
170        char const* arg = *argCursor++;
171
172        if (arg[0] != '-')
173        {
174            // We need to determine if this is a command, the confusion is that
175            // previously we can specify a test prefix as just a single positional arg.
176            // To rule this out, here it can only be a subCommand if it is just text
177
178            if (_isSubCommand(arg))
179            {
180                optionsOut->subCommand = arg;
181                // Make the first arg the command name
182                optionsOut->subCommandArgs.add(optionsOut->subCommand);
183
184                // Add all the remaining commands to subCommands
185                for (; argCursor != argEnd; ++argCursor)
186                {
187                    optionsOut->subCommandArgs.add(*argCursor);
188                }
189                // Done
190                return SLANG_OK;
191            }
192
193            positionalArgs.add(arg);
194            continue;
195        }
196
197        if (strcmp(arg, "--") == 0)
198        {
199            // Add all positional args at the end
200            while (argCursor != argEnd)
201            {
202                positionalArgs.add(*argCursor++);
203            }
204            break;
205        }
206
207        if (strcmp(arg, "-bindir") == 0)
208        {
209            if (argCursor == argEnd)
210            {
211                stdError.print("error: expected operand for '%s'\n", arg);
212                showHelp(stdError);
213                return SLANG_FAIL;
214            }
215            optionsOut->binDir = *argCursor++;
216        }
217        else if (strcmp(arg, "-use-shared-library") == 0)
218        {
219            optionsOut->defaultSpawnType = SpawnType::UseSharedLibrary;
220        }
221        else if (strcmp(arg, "-use-test-server") == 0)
222        {
223            optionsOut->defaultSpawnType = SpawnType::UseTestServer;
224        }
225        else if (strcmp(arg, "-use-fully-isolated-test-server") == 0)
226        {
227            optionsOut->defaultSpawnType = SpawnType::UseFullyIsolatedTestServer;
228        }
229        else if (strcmp(arg, "-v") == 0)
230        {
231            if (argCursor == argEnd)
232            {
233                // Default to verbose if no argument provided (backward compatibility)
234                optionsOut->verbosity = VerbosityLevel::Verbose;
235            }
236            else
237            {
238                const char* verbosityArg = *argCursor;
239                if (strcmp(verbosityArg, "verbose") == 0)
240                {
241                    optionsOut->verbosity = VerbosityLevel::Verbose;
242                    argCursor++;
243                }
244                else if (strcmp(verbosityArg, "info") == 0)
245                {
246                    optionsOut->verbosity = VerbosityLevel::Info;
247                    argCursor++;
248                }
249                else if (strcmp(verbosityArg, "failure") == 0)
250                {
251                    optionsOut->verbosity = VerbosityLevel::Failure;
252                    argCursor++;
253                }
254                else
255                {
256                    // Not a verbosity level, treat as old-style -v
257                    optionsOut->verbosity = VerbosityLevel::Verbose;
258                }
259            }
260        }
261        else if (strcmp(arg, "-hide-ignored") == 0)
262        {
263            optionsOut->hideIgnored = true;
264        }
265        else if (strcmp(arg, "-api-only") == 0)
266        {
267            optionsOut->apiOnly = true;
268        }
269        else if (strcmp(arg, "-verbose-paths") == 0)
270        {
271            optionsOut->verbosePaths = true;
272        }
273        else if (strcmp(arg, "-generate-hlsl-baselines") == 0)
274        {
275            optionsOut->generateHLSLBaselines = true;
276        }
277        else if (strcmp(arg, "-shuffle-tests") == 0)
278        {
279            optionsOut->shuffleTests = true;
280        }
281        else if (strcmp(arg, "-shuffle-seed") == 0)
282        {
283            if (argCursor == argEnd)
284            {
285                stdError.print("error: expected operand for '%s'\n", arg);
286                showHelp(stdError);
287                return SLANG_FAIL;
288            }
289            optionsOut->shuffleSeed = stringToInt(*argCursor++);
290            if (optionsOut->shuffleSeed <= 0)
291            {
292                optionsOut->shuffleSeed = 1;
293            }
294        }
295        else if (strcmp(arg, "-release") == 0)
296        {
297            // Assumed to be handle by .bat file that called us
298        }
299        else if (strcmp(arg, "-debug") == 0)
300        {
301            // Assumed to be handle by .bat file that called us
302        }
303        else if (strcmp(arg, "-configuration") == 0)
304        {
305            if (argCursor == argEnd)
306            {
307                stdError.print("error: expected operand for '%s'\n", arg);
308                showHelp(stdError);
309                return SLANG_FAIL;
310            }
311            argCursor++;
312            // Assumed to be handle by .bat file that called us
313        }
314        else if (strcmp(arg, "-platform") == 0)
315        {
316            if (argCursor == argEnd)
317            {
318                stdError.print("error: expected operand for '%s'\n", arg);
319                showHelp(stdError);
320                return SLANG_FAIL;
321            }
322            argCursor++;
323            // Assumed to be handle by .bat file that called us
324        }
325        else if (strcmp(arg, "-server-count") == 0)
326        {
327            if (argCursor == argEnd)
328            {
329                stdError.print("error: expected operand for '%s'\n", arg);
330                showHelp(stdError);
331                return SLANG_FAIL;
332            }
333            optionsOut->serverCount = stringToInt(*argCursor++);
334            if (optionsOut->serverCount <= 0)
335            {
336                optionsOut->serverCount = 1;
337            }
338        }
339        else if (strcmp(arg, "-appveyor") == 0)
340        {
341            optionsOut->outputMode = TestOutputMode::AppVeyor;
342            optionsOut->dumpOutputOnFailure = true;
343        }
344        else if (strcmp(arg, "-travis") == 0)
345        {
346            optionsOut->outputMode = TestOutputMode::Travis;
347            optionsOut->dumpOutputOnFailure = true;
348        }
349        else if (strcmp(arg, "-xunit") == 0)
350        {
351            optionsOut->outputMode = TestOutputMode::XUnit;
352        }
353        else if (strcmp(arg, "-xunit2") == 0)
354        {
355            optionsOut->outputMode = TestOutputMode::XUnit2;
356        }
357        else if (strcmp(arg, "-teamcity") == 0)
358        {
359            optionsOut->outputMode = TestOutputMode::TeamCity;
360        }
361        else if (strcmp(arg, "-category") == 0)
362        {
363            if (argCursor == argEnd)
364            {
365                stdError.print("error: expected operand for '%s'\n", arg);
366                showHelp(stdError);
367                return SLANG_FAIL;
368            }
369            auto category = categorySet->findOrError(*argCursor++);
370            if (category)
371            {
372                optionsOut->includeCategories.add(category, category);
373            }
374        }
375        else if (strcmp(arg, "-exclude") == 0)
376        {
377            if (argCursor == argEnd)
378            {
379                stdError.print("error: expected operand for '%s'\n", arg);
380                showHelp(stdError);
381                return SLANG_FAIL;
382            }
383            auto category = categorySet->findOrError(*argCursor++);
384            if (category)
385            {
386                optionsOut->excludeCategories.add(category, category);
387            }
388        }
389        else if (strcmp(arg, "-exclude-prefix") == 0)
390        {
391            if (argCursor == argEnd)
392            {
393                stdError.print("error: expected operand for '%s'\n", arg);
394                showHelp(stdError);
395                return SLANG_FAIL;
396            }
397            Slang::StringBuilder sb;
398            Slang::Path::simplify(*argCursor++, Slang::Path::SimplifyStyle::NoRoot, sb);
399            optionsOut->excludePrefixes.add(sb);
400        }
401        else if (strcmp(arg, "-api") == 0)
402        {
403            if (argCursor == argEnd)
404            {
405                stdError.print(
406                    "error: expecting an api expression (eg 'vk+dx12' or '+dx11') '%s'\n",
407                    arg);
408                showHelp(stdError);
409                return SLANG_FAIL;
410            }
411            const char* apiList = *argCursor++;
412
413            SlangResult res = RenderApiUtil::parseApiFlags(
414                UnownedStringSlice(apiList),
415                optionsOut->enabledApis,
416                &optionsOut->enabledApis);
417            if (SLANG_FAILED(res))
418            {
419                stdError.print("error: unable to parse api expression '%s'\n", apiList);
420                return res;
421            }
422        }
423        else if (strcmp(arg, "-synthesizedTestApi") == 0)
424        {
425            if (argCursor == argEnd)
426            {
427                stdError.print(
428                    "error: expected an api expression (eg 'vk+dx12' or '+dx11') '%s'\n",
429                    arg);
430                showHelp(stdError);
431                return SLANG_FAIL;
432            }
433            const char* apiList = *argCursor++;
434
435            SlangResult res = RenderApiUtil::parseApiFlags(
436                UnownedStringSlice(apiList),
437                optionsOut->synthesizedTestApis,
438                &optionsOut->synthesizedTestApis);
439            if (SLANG_FAILED(res))
440            {
441                stdError.print("error: unable to parse api expression '%s'\n", apiList);
442                return res;
443            }
444        }
445        else if (strcmp(arg, "-skip-api-detection") == 0)
446        {
447            optionsOut->skipApiDetection = true;
448        }
449        else if (strcmp(arg, "-emit-spirv-via-glsl") == 0)
450        {
451            optionsOut->emitSPIRVDirectly = false;
452        }
453        else if (strcmp(arg, "-capability") == 0)
454        {
455            if (argCursor == argEnd)
456            {
457                stdError.print("error: expected operand for '%s'\n", arg);
458                showHelp(stdError);
459                return SLANG_FAIL;
460            }
461            optionsOut->capabilities.add(*argCursor++);
462        }
463        else if (strcmp(arg, "-ignore-abort-msg") == 0)
464        {
465            optionsOut->ignoreAbortMsg = true;
466#ifdef _MSC_VER
467            _set_abort_behavior(0, _WRITE_ABORT_MSG);
468#endif
469        }
470        else if (strcmp(arg, "-expected-failure-list") == 0)
471        {
472            if (argCursor == argEnd)
473            {
474                stdError.print("error: expected operand for '%s'\n", arg);
475                showHelp(stdError);
476                return SLANG_FAIL;
477            }
478            auto fileName = *argCursor++;
479            String text;
480            File::readAllText(fileName, text);
481            List<UnownedStringSlice> lines;
482            StringUtil::split(text.getUnownedSlice(), '\n', lines);
483            for (auto line : lines)
484            {
485                // Remove comments (everything after '#' character)
486                auto trimmedLine = line;
487                auto commentIndex = line.indexOf('#');
488                if (commentIndex != -1)
489                {
490                    trimmedLine = line.head(commentIndex);
491                }
492
493                // Trim whitespace and skip empty lines
494                trimmedLine = trimmedLine.trim();
495                if (trimmedLine.getLength() > 0)
496                {
497                    optionsOut->expectedFailureList.add(trimmedLine);
498                }
499            }
500        }
501        else if (strcmp(arg, "-test-dir") == 0)
502        {
503            if (argCursor == argEnd)
504            {
505                stdError.print("error: expected operand for '%s'\n", arg);
506                showHelp(stdError);
507                return SLANG_FAIL;
508            }
509            optionsOut->testDir = *argCursor++;
510        }
511        else if (strcmp(arg, "-show-adapter-info") == 0)
512        {
513            optionsOut->showAdapterInfo = true;
514        }
515        else if (strcmp(arg, "-skip-reference-image-generation") == 0)
516        {
517            optionsOut->skipReferenceImageGeneration = true;
518        }
519        else if (strcmp(arg, "-enable-debug-layers") == 0)
520        {
521            optionsOut->enableDebugLayers = true;
522
523            if (argCursor == argEnd)
524            {
525                stdError.print("error: expected operand for '%s'\n", arg);
526                showHelp(stdError);
527                return SLANG_FAIL;
528            }
529
530            // Check for false variants
531            const char* value = *argCursor++;
532            if (value[0] == 'f' || value[0] == 'F' || value[0] == 'n' || value[0] == 'N' ||
533                value[0] == '0' ||
534                ((value[0] == 'o' || value[0] == 'O') && (value[1] == 'f' || value[1] == 'F')))
535            {
536                optionsOut->enableDebugLayers = false;
537            }
538        }
539        else if (strcmp(arg, "-cache-rhi-device") == 0)
540        {
541            optionsOut->cacheRhiDevice = true;
542
543            if (argCursor == argEnd)
544            {
545                stdError.print("error: expected operand for '%s'\n", arg);
546                showHelp(stdError);
547                return SLANG_FAIL;
548            }
549
550            // Check for false variants
551            const char* value = *argCursor++;
552            if (value[0] == 'f' || value[0] == 'F' || value[0] == 'n' || value[0] == 'N' ||
553                value[0] == '0' ||
554                ((value[0] == 'o' || value[0] == 'O') && (value[1] == 'f' || value[1] == 'F')))
555            {
556                optionsOut->cacheRhiDevice = false;
557            }
558        }
559#if _DEBUG
560        else if (strcmp(arg, "-disable-debug-layers") == 0)
561        {
562            stdError.print("warning: '-disable-debug-layers' is deprecated, use "
563                           "'-enable-debug-layers false'\n");
564            optionsOut->enableDebugLayers = false;
565        }
566#endif
567        else
568        {
569            stdError.print("unknown option '%s'\n", arg);
570            showHelp(stdError);
571            return SLANG_FAIL;
572        }
573    }
574
575    {
576        // Find out what apis are available
577        const int availableApis = RenderApiUtil::getAvailableApis();
578        // Only allow apis we know are available
579        optionsOut->enabledApis &= availableApis;
580
581        // Can only synth for apis that are available
582        optionsOut->synthesizedTestApis &= optionsOut->enabledApis;
583    }
584
585
586    // first positional argument is source shader path
587    optionsOut->testPrefixes.clear();
588    optionsOut->testPrefixes.reserve(positionalArgs.getCount());
589    for (auto testPrefix : positionalArgs)
590    {
591        Slang::StringBuilder sb;
592        Slang::Path::simplify(testPrefix, Slang::Path::SimplifyStyle::NoRoot, sb);
593        optionsOut->testPrefixes.add(sb);
594    }
595
596    if (optionsOut->binDir.getLength() == 0)
597    {
598        // If the binDir isn't set try using the path to the executable
599        String exePath = Path::getExecutablePath();
600        if (exePath.getLength())
601        {
602            optionsOut->binDir = Path::getParentDirectory(exePath);
603        }
604    }
605
606    if (optionsOut->testDir.getLength() == 0)
607    {
608        // If the test directory isn't set, use the "tests" directory
609        optionsOut->testDir = String("tests/");
610    }
611
612    return SLANG_OK;
613}