summaryrefslogtreecommitdiffstats
path: root/tools/slang-test
diff options
context:
space:
mode:
authorTim Foley <tfoley@nvidia.com>2017-06-09 11:34:21 -0700
committerTim Foley <tfoley@nvidia.com>2017-06-09 13:44:59 -0700
commitfcf83dbf9effab3bd98bad2b83b2468b7eb05cfd (patch)
tree41047c94883b86ec085a81597391ce3ef557cd43 /tools/slang-test
parent52e8d4b9a27ab0060f874c3a63ab531847be35c0 (diff)
Initial import of code.
Diffstat (limited to 'tools/slang-test')
-rw-r--r--tools/slang-test/main.cpp1030
-rw-r--r--tools/slang-test/os.cpp370
-rw-r--r--tools/slang-test/os.h160
-rw-r--r--tools/slang-test/slang-test.vcxproj277
-rw-r--r--tools/slang-test/slang-test.vcxproj.filters30
5 files changed, 1867 insertions, 0 deletions
diff --git a/tools/slang-test/main.cpp b/tools/slang-test/main.cpp
new file mode 100644
index 000000000..d8a04a050
--- /dev/null
+++ b/tools/slang-test/main.cpp
@@ -0,0 +1,1030 @@
+// main.cpp
+
+#include "../../source/core/slang-io.h"
+
+using namespace CoreLib::Basic;
+using namespace CoreLib::IO;
+
+#include "os.h"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "external/stb/stb_image.h"
+
+
+#ifdef _WIN32
+#define SLANG_TEST_SUPPORT_HLSL 1
+#include <d3dcompiler.h>
+#endif
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+struct Options
+{
+ char const* appName = "slang-test";
+
+ // Directory to use when looking for binaries to run
+ char const* binDir = "";
+
+ // only run test cases with names that have this prefix
+ char const* testPrefix = nullptr;
+
+ // generate extra output (notably: command lines we run)
+ bool shouldBeVerbose = false;
+
+ // force generation of baselines for HLSL tests
+ bool generateHLSLBaselines = false;
+};
+Options options;
+
+void parseOptions(int* argc, char** argv)
+{
+ int argCount = *argc;
+ char const* const* argCursor = argv;
+ char const* const* argEnd = argCursor + argCount;
+
+ char const** writeCursor = (char const**) argv;
+
+ // first argument is the application name
+ if( argCursor != argEnd )
+ {
+ options.appName = *argCursor++;
+ }
+
+ // now iterate over arguments to collect options
+ while(argCursor != argEnd)
+ {
+ char const* arg = *argCursor++;
+ if( arg[0] != '-' )
+ {
+ *writeCursor++ = arg;
+ continue;
+ }
+
+ if( strcmp(arg, "--") == 0 )
+ {
+ while(argCursor != argEnd)
+ {
+ char const* arg = *argCursor++;
+ *writeCursor++ = arg;
+ }
+ break;
+ }
+
+ if( strcmp(arg, "--bindir") == 0 )
+ {
+ if( argCursor == argEnd )
+ {
+ fprintf(stderr, "error: expected operand for '%s'\n", arg);
+ exit(1);
+ }
+ options.binDir = *argCursor++;
+ }
+ else if( strcmp(arg, "-v") == 0 )
+ {
+ options.shouldBeVerbose = true;
+ }
+ else if( strcmp(arg, "-generate-hlsl-baselines") == 0 )
+ {
+ options.generateHLSLBaselines = true;
+ }
+ else if( strcmp(arg, "-release") == 0 )
+ {
+ // Assumed to be handle by .bat file that called us
+ }
+ else if( strcmp(arg, "-debug") == 0 )
+ {
+ // Assumed to be handle by .bat file that called us
+ }
+ else
+ {
+ fprintf(stderr, "unknown option '%s'\n", arg);
+ exit(1);
+ }
+ }
+
+ // any arguments left over were positional arguments
+ argCount = (int)(writeCursor - argv);
+ argCursor = argv;
+ argEnd = argCursor + argCount;
+
+ // first positional argument is a "filter" to apply
+ if( argCursor != argEnd )
+ {
+ options.testPrefix = *argCursor++;
+ }
+
+ // any remaining arguments represent an error
+ if(argCursor != argEnd)
+ {
+ fprintf(stderr, "unexpected arguments\n");
+ exit(1);
+ }
+
+ *argc = 0;
+}
+
+// Called for an error in the test-runner (not for an error involving
+// a test itself).
+void error(char const* message, ...)
+{
+ fprintf(stderr, "error: ");
+
+ va_list args;
+ va_start(args, message);
+ vfprintf(stderr, message, args);
+ va_end(args);
+
+ fprintf(stderr, "\n");
+}
+
+enum TestResult
+{
+ kTestResult_Fail,
+ kTestResult_Pass,
+ kTestResult_Ignored,
+};
+
+bool match(char const** ioCursor, char const* expected)
+{
+ char const* cursor = *ioCursor;
+ while(*expected && *cursor == *expected)
+ {
+ cursor++;
+ expected++;
+ }
+ if(*expected != 0) return false;
+
+ *ioCursor = cursor;
+ return true;
+}
+
+void skipHorizontalSpace(char const** ioCursor)
+{
+ char const* cursor = *ioCursor;
+ for( ;;)
+ {
+ switch( *cursor )
+ {
+ case ' ':
+ case '\t':
+ cursor++;
+ continue;
+
+ default:
+ break;
+ }
+
+ break;
+ }
+ *ioCursor = cursor;
+}
+
+void skipToEndOfLine(char const** ioCursor)
+{
+ char const* cursor = *ioCursor;
+ for( ;;)
+ {
+ int c = *cursor;
+ switch( c )
+ {
+ default:
+ cursor++;
+ continue;
+
+ case '\r': case '\n':
+ {
+ cursor++;
+ int d = *cursor;
+ if( (c ^ d) == ('\r' ^ '\n') )
+ {
+ cursor++;
+ }
+ }
+ // fall through to:
+ case 0:
+ *ioCursor = cursor;
+ return;
+ }
+ }
+}
+
+String getString(char const* textBegin, char const* textEnd)
+{
+ StringBuilder sb;
+ sb.Append(textBegin, textEnd - textBegin);
+ return sb.ProduceString();
+}
+
+String collectRestOfLine(char const** ioCursor)
+{
+ char const* cursor = *ioCursor;
+
+ char const* textBegin = cursor;
+ skipToEndOfLine(&cursor);
+ char const* textEnd = cursor;
+
+ *ioCursor = cursor;
+ return getString(textBegin, textEnd);
+}
+
+// Optiosn for a particular test
+struct TestOptions
+{
+ String command;
+ List<String> args;
+};
+
+// Information on tests to run for a particular file
+struct FileTestList
+{
+ List<TestOptions> tests;
+};
+
+TestResult gatherTestOptions(
+ char const** ioCursor,
+ FileTestList* testList)
+{
+ char const* cursor = *ioCursor;
+
+ // Start by scanning for the sub-command name:
+ char const* commandStart = cursor;
+ for(;;)
+ {
+ switch(*cursor)
+ {
+ default:
+ cursor++;
+ continue;
+
+ case ':':
+ break;
+
+ case 0: case '\r': case '\n':
+ return kTestResult_Fail;
+ }
+
+ break;
+ }
+ char const* commandEnd = cursor;
+ if(*cursor == ':')
+ cursor++;
+
+ TestOptions testOptions;
+ testOptions.command = getString(commandStart, commandEnd);
+
+ // Now scan for arguments. For now we just assume that
+ // any whitespace separation indicates a new argument
+ // (we don't support quoting)
+ for(;;)
+ {
+ skipHorizontalSpace(&cursor);
+
+ // End of line? then no more options.
+ switch( *cursor )
+ {
+ case 0: case '\r': case '\n':
+ skipToEndOfLine(&cursor);
+ testList->tests.Add(testOptions);
+ return kTestResult_Pass;
+
+ default:
+ break;
+ }
+
+ // Let's try to read one option
+ char const* argBegin = cursor;
+ for(;;)
+ {
+ switch( *cursor )
+ {
+ default:
+ cursor++;
+ continue;
+
+ case 0: case '\r': case '\n': case ' ': case '\t':
+ break;
+ }
+
+ break;
+ }
+ char const* argEnd = cursor;
+ assert(argBegin != argEnd);
+
+ testOptions.args.Add(getString(argBegin, argEnd));
+ }
+}
+
+// Try to read command-line options from the test file itself
+TestResult gatherTestsForFile(
+ String filePath,
+ FileTestList* testList)
+{
+ String fileContents;
+ try
+ {
+ fileContents = CoreLib::IO::File::ReadAllText(filePath);
+ }
+ catch (CoreLib::IO::IOException)
+ {
+ return kTestResult_Fail;
+ }
+
+
+ // Walk through the lines of the file, looking for test commands
+ char const* cursor = fileContents.begin();
+
+ while(*cursor)
+ {
+ // We are at the start of a line of input.
+
+ skipHorizontalSpace(&cursor);
+
+ // Look for a pattern that matches what we want
+ if(match(&cursor, "//TEST:"))
+ {
+ if(gatherTestOptions(&cursor, testList) != kTestResult_Pass)
+ return kTestResult_Fail;
+ }
+ else if(match(&cursor, "//TEST_IGNORE_FILE"))
+ {
+ return kTestResult_Ignored;
+ }
+ else
+ {
+ skipToEndOfLine(&cursor);
+ }
+ }
+
+ return kTestResult_Pass;
+}
+
+OSError spawnAndWait(String testPath, OSProcessSpawner& spawner)
+{
+ if( options.shouldBeVerbose )
+ {
+ fprintf(stderr, "%s\n", spawner.commandLine_.Buffer());
+ }
+
+ OSError err = spawner.spawnAndWaitForCompletion();
+ if (err != kOSError_None)
+ {
+ error("failed to run test '%S'", testPath.ToWString());
+ }
+ return err;
+}
+
+String getOutput(OSProcessSpawner& spawner)
+{
+ OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
+
+ String standardOuptut = spawner.getStandardOutput();
+ String standardError = spawner.getStandardError();
+
+ // We construct a single output string that captures the results
+ StringBuilder actualOutputBuilder;
+ actualOutputBuilder.Append("result code = ");
+ actualOutputBuilder.Append(resultCode);
+ actualOutputBuilder.Append("\nstandard error = {\n");
+ actualOutputBuilder.Append(standardError);
+ actualOutputBuilder.Append("}\nstandard output = {\n");
+ actualOutputBuilder.Append(standardOuptut);
+ actualOutputBuilder.Append("}\n");
+
+ return actualOutputBuilder.ProduceString();
+}
+
+struct TestInput
+{
+ String filePath;
+ TestOptions const* testOptions;
+ FileTestList const* testList;
+};
+
+typedef TestResult (*TestCallback)(TestInput& input);
+
+TestResult runSimpleTest(TestInput& input)
+{
+ // need to execute the stand-alone Slang compiler on the file, and compare its output to what we expect
+
+ auto filePath = input.filePath;
+
+ OSProcessSpawner spawner;
+
+ spawner.pushExecutableName(String(options.binDir) + "slangc.exe");
+ spawner.pushArgument(filePath);
+
+ for( auto arg : input.testOptions->args )
+ {
+ spawner.pushArgument(arg);
+ }
+
+ if (spawnAndWait(filePath, spawner) != kOSError_None)
+ {
+ return kTestResult_Fail;
+ }
+
+ String actualOutput = getOutput(spawner);
+
+ String expectedOutputPath = filePath + ".expected";
+ String expectedOutput;
+ try
+ {
+ expectedOutput = CoreLib::IO::File::ReadAllText(expectedOutputPath);
+ }
+ catch (CoreLib::IO::IOException)
+ {
+ }
+
+ // If no expected output file was found, then we
+ // expect everything to be empty
+ if (expectedOutput.Length() == 0)
+ {
+ expectedOutput = "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n";
+ }
+
+ TestResult result = kTestResult_Pass;
+
+ // Otherwise we compare to the expected output
+ if (actualOutput != expectedOutput)
+ {
+ result = kTestResult_Fail;
+ }
+
+ // If the test failed, then we write the actual output to a file
+ // so that we can easily diff it from the command line and
+ // diagnose the problem.
+ if (result == kTestResult_Fail)
+ {
+ String actualOutputPath = filePath + ".actual";
+ CoreLib::IO::File::WriteAllText(actualOutputPath, actualOutput);
+ }
+
+ return result;
+}
+
+#ifdef SLANG_TEST_SUPPORT_HLSL
+TestResult generateHLSLBaseline(TestInput& input)
+{
+ auto filePath = input.filePath;
+
+ OSProcessSpawner spawner;
+ spawner.pushExecutableName(String(options.binDir) + "slangc.exe");
+ spawner.pushArgument(filePath);
+
+ for( auto arg : input.testOptions->args )
+ {
+ spawner.pushArgument(arg);
+ }
+
+ spawner.pushArgument("-target");
+ spawner.pushArgument("dxbc-assembly");
+ spawner.pushArgument("-pass-through");
+ spawner.pushArgument("fxc");
+
+ if (spawnAndWait(filePath, spawner) != kOSError_None)
+ {
+ return kTestResult_Fail;
+ }
+
+ String expectedOutput = getOutput(spawner);
+ String expectedOutputPath = filePath + ".expected";
+ try
+ {
+ CoreLib::IO::File::WriteAllText(expectedOutputPath, expectedOutput);
+ }
+ catch (CoreLib::IO::IOException)
+ {
+ return kTestResult_Fail;
+ }
+ return kTestResult_Pass;
+}
+
+TestResult runHLSLComparisonTest(TestInput& input)
+{
+ auto filePath = input.filePath;
+
+ // We will use the Microsoft compiler to generate out expected output here
+ String expectedOutputPath = filePath + ".expected";
+
+ // Generate the expected output using standard HLSL compiler
+ generateHLSLBaseline(input);
+
+ // need to execute the stand-alone Slang compiler on the file, and compare its output to what we expect
+
+ OSProcessSpawner spawner;
+
+ spawner.pushExecutableName(String(options.binDir) + "slangc.exe");
+ spawner.pushArgument(filePath);
+
+ for( auto arg : input.testOptions->args )
+ {
+ spawner.pushArgument(arg);
+ }
+
+ // TODO: The compiler should probably define this automatically...
+ spawner.pushArgument("-D");
+ spawner.pushArgument("__SLANG__");
+
+ spawner.pushArgument("-target");
+ spawner.pushArgument("dxbc-assembly");
+
+ if (spawnAndWait(filePath, spawner) != kOSError_None)
+ {
+ return kTestResult_Fail;
+ }
+
+ // We ignore output to stdout, and only worry about what the compiler
+ // wrote to stderr.
+
+ OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
+
+ String standardOuptut = spawner.getStandardOutput();
+ String standardError = spawner.getStandardError();
+
+ // We construct a single output string that captures the results
+ StringBuilder actualOutputBuilder;
+ actualOutputBuilder.Append("result code = ");
+ actualOutputBuilder.Append(resultCode);
+ actualOutputBuilder.Append("\nstandard error = {\n");
+ actualOutputBuilder.Append(standardError);
+ actualOutputBuilder.Append("}\nstandard output = {\n");
+ actualOutputBuilder.Append(standardOuptut);
+ actualOutputBuilder.Append("}\n");
+
+ String actualOutput = actualOutputBuilder.ProduceString();
+
+ String expectedOutput;
+ try
+ {
+ expectedOutput = CoreLib::IO::File::ReadAllText(expectedOutputPath);
+ }
+ catch (CoreLib::IO::IOException)
+ {
+ }
+
+ TestResult result = kTestResult_Pass;
+
+ // If no expected output file was found, then we
+ // expect everything to be empty
+ if (expectedOutput.Length() == 0)
+ {
+ if (resultCode != 0) result = kTestResult_Fail;
+ if (standardError.Length() != 0) result = kTestResult_Fail;
+ if (standardOuptut.Length() != 0) result = kTestResult_Fail;
+ }
+ // Otherwise we compare to the expected output
+ else if (actualOutput != expectedOutput)
+ {
+ result = kTestResult_Fail;
+ }
+
+ // If the test failed, then we write the actual output to a file
+ // so that we can easily diff it from the command line and
+ // diagnose the problem.
+ if (result == kTestResult_Fail)
+ {
+ String actualOutputPath = filePath + ".actual";
+ CoreLib::IO::File::WriteAllText(actualOutputPath, actualOutput);
+ }
+
+ return result;
+}
+#endif
+
+TestResult doGLSLComparisonTestRun(
+ TestInput& input,
+ char const* langDefine,
+ char const* passThrough,
+ char const* outputKind,
+ String* outOutput)
+{
+ auto filePath = input.filePath;
+
+ OSProcessSpawner spawner;
+
+ spawner.pushExecutableName(String(options.binDir) + "slangc.exe");
+ spawner.pushArgument(filePath);
+
+ if( langDefine )
+ {
+ spawner.pushArgument("-D");
+ spawner.pushArgument(langDefine);
+ }
+
+ if( passThrough )
+ {
+ spawner.pushArgument("-pass-through");
+ spawner.pushArgument(passThrough);
+ }
+
+ spawner.pushArgument("-no-checking");
+
+ spawner.pushArgument("-target");
+ spawner.pushArgument("spirv-assembly");
+
+ for( auto arg : input.testOptions->args )
+ {
+ spawner.pushArgument(arg);
+ }
+
+ if (spawnAndWait(filePath, spawner) != kOSError_None)
+ {
+ return kTestResult_Fail;
+ }
+
+ OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
+
+ String standardOuptut = spawner.getStandardOutput();
+ String standardError = spawner.getStandardError();
+
+ // We construct a single output string that captures the results
+ StringBuilder outputBuilder;
+ outputBuilder.Append("result code = ");
+ outputBuilder.Append(resultCode);
+ outputBuilder.Append("\nstandard error = {\n");
+ outputBuilder.Append(standardError);
+ outputBuilder.Append("}\nstandard output = {\n");
+ outputBuilder.Append(standardOuptut);
+ outputBuilder.Append("}\n");
+
+ String outputPath = filePath + outputKind;
+ String output = outputBuilder.ProduceString();
+
+ *outOutput = output;
+
+ return kTestResult_Pass;
+}
+
+TestResult runGLSLComparisonTest(TestInput& input)
+{
+ auto filePath = input.filePath;
+
+ String expectedOutput;
+ String actualOutput;
+
+ TestResult hlslResult = doGLSLComparisonTestRun(input, "__GLSL__", "glslang", ".expected", &expectedOutput);
+ TestResult slangResult = doGLSLComparisonTestRun(input, "__SLANG__", nullptr, ".actual", &actualOutput);
+
+ CoreLib::IO::File::WriteAllText(filePath + ".expected", expectedOutput);
+ CoreLib::IO::File::WriteAllText(filePath + ".actual", actualOutput);
+
+ if( hlslResult == kTestResult_Fail ) return kTestResult_Fail;
+ if( slangResult == kTestResult_Fail ) return kTestResult_Fail;
+
+ if (actualOutput != expectedOutput)
+ {
+ return kTestResult_Fail;
+ }
+
+ return kTestResult_Pass;
+}
+
+
+
+TestResult doRenderComparisonTestRun(TestInput& input, char const* langOption, char const* outputKind, String* outOutput)
+{
+ // TODO: delete any existing files at the output path(s) to avoid stale outputs leading to a false pass
+
+ auto filePath = input.filePath;
+
+ OSProcessSpawner spawner;
+
+ spawner.pushExecutableName(String(options.binDir) + "render-test.exe");
+ spawner.pushArgument(filePath);
+
+ for( auto arg : input.testOptions->args )
+ {
+ spawner.pushArgument(arg);
+ }
+
+ spawner.pushArgument(langOption);
+ spawner.pushArgument("-o");
+ spawner.pushArgument(filePath + outputKind + ".png");
+
+ if (spawnAndWait(filePath, spawner) != kOSError_None)
+ {
+ return kTestResult_Fail;
+ }
+
+ OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
+
+ String standardOuptut = spawner.getStandardOutput();
+ String standardError = spawner.getStandardError();
+
+ // We construct a single output string that captures the results
+ StringBuilder outputBuilder;
+ outputBuilder.Append("result code = ");
+ outputBuilder.Append(resultCode);
+ outputBuilder.Append("\nstandard error = {\n");
+ outputBuilder.Append(standardError);
+ outputBuilder.Append("}\nstandard output = {\n");
+ outputBuilder.Append(standardOuptut);
+ outputBuilder.Append("}\n");
+
+ String outputPath = filePath + outputKind;
+ String output = outputBuilder.ProduceString();
+
+ *outOutput = output;
+
+ return kTestResult_Pass;
+}
+
+TestResult doImageComparison(String const& filePath)
+{
+ // Allow a difference in the low bits of the 8-bit result, just to play it safe
+ static const int kAbsoluteDiffCutoff = 2;
+
+ // Allow a relatie 1% difference
+ static const float kRelativeDiffCutoff = 0.01f;
+
+ String expectedPath = filePath + ".expected.png";
+ String actualPath = filePath + ".actual.png";
+
+ int expectedX, expectedY, expectedN;
+ int actualX, actualY, actualN;
+
+
+ unsigned char* expectedData = stbi_load(expectedPath.begin(), &expectedX, &expectedY, &expectedN, 0);
+ unsigned char* actualData = stbi_load(actualPath.begin(), &actualX, &actualY, &actualN, 0);
+
+ if(!expectedData) return kTestResult_Fail;
+ if(!actualData) return kTestResult_Fail;
+
+ if(expectedX != actualX) return kTestResult_Fail;
+ if(expectedY != actualY) return kTestResult_Fail;
+ if(expectedN != actualN) return kTestResult_Fail;
+
+ unsigned char* expectedCursor = expectedData;
+ unsigned char* actualCursor = actualData;
+
+ for( int y = 0; y < actualY; ++y )
+ for( int x = 0; x < actualX; ++x )
+ for( int n = 0; n < actualN; ++n )
+ {
+ int expectedVal = *expectedCursor++;
+ int actualVal = *actualCursor++;
+
+ int absoluteDiff = actualVal - expectedVal;
+ if(absoluteDiff < 0) absoluteDiff = -absoluteDiff;
+
+ if( absoluteDiff < kAbsoluteDiffCutoff )
+ {
+ // There might be a difference, but we'll consider it to be inside tolerance
+ continue;
+ }
+
+ if( expectedVal != 0 )
+ {
+ float relativeDiff = fabsf(float(actualVal) - float(expectedVal)) / float(expectedVal);
+
+ if( relativeDiff < kRelativeDiffCutoff )
+ {
+ // relative difference was small enough
+ continue;
+ }
+ }
+
+ // TODO: may need to do some local search sorts of things, to deal with
+ // cases where vertex shader results lead to rendering that is off
+ // by one pixel...
+
+ // There was a difference we couldn't excuse!
+ return kTestResult_Fail;
+ }
+
+ return kTestResult_Pass;
+}
+
+TestResult runHLSLRenderComparisonTestImpl(
+ TestInput& input,
+ char const* expectedArg,
+ char const* actualArg)
+{
+ auto filePath = input.filePath;
+
+ String expectedOutput;
+ String actualOutput;
+
+ TestResult hlslResult = doRenderComparisonTestRun(input, expectedArg, ".expected", &expectedOutput);
+ TestResult slangResult = doRenderComparisonTestRun(input, actualArg, ".actual", &actualOutput);
+
+ CoreLib::IO::File::WriteAllText(filePath + ".expected", expectedOutput);
+ CoreLib::IO::File::WriteAllText(filePath + ".actual", actualOutput);
+
+ if( hlslResult == kTestResult_Fail ) return kTestResult_Fail;
+ if( slangResult == kTestResult_Fail ) return kTestResult_Fail;
+
+ if (actualOutput != expectedOutput)
+ {
+ return kTestResult_Fail;
+ }
+
+ // Next do an image comparison on the expected output images!
+
+ TestResult imageCompareResult = doImageComparison(filePath);
+ if(imageCompareResult != kTestResult_Pass)
+ return imageCompareResult;
+
+ return kTestResult_Pass;
+}
+
+TestResult runHLSLRenderComparisonTest(TestInput& input)
+{
+ return runHLSLRenderComparisonTestImpl(input, "-hlsl", "-slang");
+}
+
+TestResult runHLSLCrossCompileRenderComparisonTest(TestInput& input)
+{
+ return runHLSLRenderComparisonTestImpl(input, "-slang", "-glsl-cross");
+}
+
+TestResult runTest(
+ String const& filePath,
+ TestOptions const& testOptions,
+ FileTestList const& testList)
+{
+ // based on command name, dispatch to an appropriate callback
+ static const struct TestCommands
+ {
+ char const* name;
+ TestCallback callback;
+ } kTestCommands[] = {
+ { "SIMPLE", &runSimpleTest },
+ { "COMPARE_HLSL", &runHLSLComparisonTest },
+ { "COMPARE_HLSL_RENDER", &runHLSLRenderComparisonTest },
+ { "COMPARE_HLSL_CROSS_COMPILE_RENDER", &runHLSLCrossCompileRenderComparisonTest},
+ { "COMPARE_GLSL", &runGLSLComparisonTest },
+ { nullptr, nullptr },
+ };
+
+ for( auto ii = kTestCommands; ii->name; ++ii )
+ {
+ if(testOptions.command != ii->name)
+ continue;
+
+ TestInput testInput;
+ testInput.filePath = filePath;
+ testInput.testOptions = &testOptions;
+ testInput.testList = &testList;
+
+ return ii->callback(testInput);
+ }
+
+ // No actual test runner found!
+
+ return kTestResult_Fail;
+}
+
+
+struct TestContext
+{
+ int totalTestCount;
+ int passedTestCount;
+ int failedTestCount;
+};
+
+void runTestsOnFile(
+ TestContext* context,
+ String filePath)
+{
+ // Gather a list of tests to run
+ FileTestList testList;
+
+ if( gatherTestsForFile(filePath, &testList) == kTestResult_Ignored )
+ {
+ // Test was explicitly ignored
+ return;
+ }
+
+ // Note cases where a test file exists, but we found nothing to run
+ if( testList.tests.Count() == 0 )
+ {
+ context->totalTestCount++;
+ context->failedTestCount++;
+
+ printf("FAILED test: '%S' (no test commands found)\n", filePath.ToWString());
+ return;
+ }
+
+ // We have found a test to run!
+ int subTestCount = 0;
+ for( auto& tt : testList.tests )
+ {
+ context->totalTestCount++;
+
+ int subTestIndex = subTestCount++;
+
+ TestResult result = runTest(filePath, tt, testList);
+ if(result == kTestResult_Ignored)
+ return;
+
+ if (result == kTestResult_Pass)
+ {
+ printf("passed");
+ context->passedTestCount++;
+ }
+ else
+ {
+ printf("FAILED");
+ context->failedTestCount++;
+ }
+
+ printf(" test: '%S'", filePath.ToWString());
+ if( subTestIndex )
+ {
+ printf(" subtest:%d", subTestIndex);
+ }
+ printf("\n");
+ }
+}
+
+
+static bool endsWithAllowedExtension(
+ TestContext* context,
+ String filePath)
+{
+ char const* allowedExtensions[] = {
+ ".slang",
+ ".hlsl",
+ ".fx",
+ ".glsl",
+ ".vert",
+ ".frag",
+ ".geom",
+ ".tesc",
+ ".tese",
+ ".comp",
+ nullptr };
+
+ for( auto ii = allowedExtensions; *ii; ++ii )
+ {
+ if(filePath.EndsWith(*ii))
+ return true;
+ }
+
+ return false;
+}
+
+static bool shouldRunTest(
+ TestContext* context,
+ String filePath)
+{
+ if(!endsWithAllowedExtension(context, filePath))
+ return false;
+
+ if( options.testPrefix )
+ {
+ if( strncmp(options.testPrefix, filePath.begin(), strlen(options.testPrefix)) != 0 )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void runTestsInDirectory(
+ TestContext* context,
+ String directoryPath)
+{
+ for (auto file : osFindFilesInDirectory(directoryPath))
+ {
+ if( shouldRunTest(context, file) )
+ {
+ runTestsOnFile(context, file);
+ }
+ }
+ for (auto subdir : osFindChildDirectories(directoryPath))
+ {
+ runTestsInDirectory(context, subdir);
+ }
+}
+
+//
+
+int main(
+ int argc,
+ char** argv)
+{
+ parseOptions(&argc, argv);
+
+ TestContext context = { 0 };
+
+ // Enumerate test files according to policy
+ // TODO: add more directories to this list
+ // TODO: allow for a command-line argument to select a particular directory
+ runTestsInDirectory(&context, "tests/");
+
+ if (!context.totalTestCount)
+ {
+ printf("no tests run\n");
+ return 0;
+ }
+
+ printf("\n===\n%d%% of tests passed (%d/%d)\n===\n\n", (context.passedTestCount*100) / context.totalTestCount, context.passedTestCount, context.totalTestCount);
+ return context.passedTestCount == context.totalTestCount ? 0 : 1;
+}
diff --git a/tools/slang-test/os.cpp b/tools/slang-test/os.cpp
new file mode 100644
index 000000000..8f5172ae6
--- /dev/null
+++ b/tools/slang-test/os.cpp
@@ -0,0 +1,370 @@
+// os.cpp
+#include "os.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+using namespace CoreLib::Basic;
+
+// Platform-specific code follows
+
+#ifdef _WIN32
+
+#include <Windows.h>
+
+static bool advance(OSFindFilesResult& result)
+{
+ return FindNextFileW(result.findHandle_, &result.fileData_) != 0;
+}
+
+static bool adjustToValidResult(OSFindFilesResult& result)
+{
+ for (;;)
+ {
+ if ((result.fileData_.dwFileAttributes & result.requiredMask_) != result.requiredMask_)
+ goto skip;
+
+ if ((result.fileData_.dwFileAttributes & result.disallowedMask_) != 0)
+ goto skip;
+
+ if (wcscmp(result.fileData_.cFileName, L".") == 0)
+ goto skip;
+
+ if (wcscmp(result.fileData_.cFileName, L"..") == 0)
+ goto skip;
+
+ result.filePath_ = result.directoryPath_ + String::FromWString(result.fileData_.cFileName);
+ if (result.fileData_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ result.filePath_ = result.filePath_ + "/";
+
+ return true;
+
+ skip:
+ if (!advance(result))
+ return false;
+ }
+}
+
+
+bool OSFindFilesResult::findNextFile()
+{
+ if (!advance(*this)) return false;
+ return adjustToValidResult(*this);
+}
+
+OSFindFilesResult osFindFilesInDirectoryMatchingPattern(
+ CoreLib::Basic::String directoryPath,
+ CoreLib::Basic::String pattern)
+{
+ // TODO: add separator to end of directory path if needed
+
+ String searchPath = directoryPath + pattern;
+
+ OSFindFilesResult result;
+ HANDLE findHandle = FindFirstFileW(
+ searchPath.ToWString(),
+ &result.fileData_);
+
+ result.directoryPath_ = directoryPath;
+ result.findHandle_ = findHandle;
+ result.requiredMask_ = 0;
+ result.disallowedMask_ = FILE_ATTRIBUTE_DIRECTORY;
+
+ if (findHandle == INVALID_HANDLE_VALUE)
+ {
+ result.findHandle_ = NULL;
+ result.error_ = kOSError_FileNotFound;
+ return result;
+ }
+
+ result.error_ = kOSError_None;
+ if (!adjustToValidResult(result))
+ {
+ result.findHandle_ = NULL;
+ }
+ return result;
+}
+
+OSFindFilesResult osFindFilesInDirectory(
+ CoreLib::Basic::String directoryPath)
+{
+ return osFindFilesInDirectoryMatchingPattern(directoryPath, "*");
+}
+
+OSFindFilesResult osFindChildDirectories(
+ CoreLib::Basic::String directoryPath)
+{
+ // TODO: add separator to end of directory path if needed
+
+ String searchPath = directoryPath + "*";
+
+ OSFindFilesResult result;
+ HANDLE findHandle = FindFirstFileW(
+ searchPath.ToWString(),
+ &result.fileData_);
+
+ result.directoryPath_ = directoryPath;
+ result.findHandle_ = findHandle;
+ result.requiredMask_ = FILE_ATTRIBUTE_DIRECTORY;
+ result.disallowedMask_ = 0;
+
+ if (findHandle == INVALID_HANDLE_VALUE)
+ {
+ result.findHandle_ = NULL;
+ result.error_ = kOSError_FileNotFound;
+ return result;
+ }
+
+ result.error_ = kOSError_None;
+ if (!adjustToValidResult(result))
+ {
+ result.findHandle_ = NULL;
+ }
+ return result;
+}
+
+// OSProcessSpawner
+
+struct OSProcessSpawner_ReaderThreadInfo
+{
+ HANDLE file;
+ String output;
+};
+
+static DWORD WINAPI osReaderThreadProc(LPVOID threadParam)
+{
+ OSProcessSpawner_ReaderThreadInfo* info = (OSProcessSpawner_ReaderThreadInfo*)threadParam;
+ HANDLE file = info->file;
+
+ static const int kChunkSize = 1024;
+ char buffer[kChunkSize];
+
+ StringBuilder outputBuilder;
+
+ // We need to re-write the output to deal with line
+ // endings, so we check for paired '\r' and '\n'
+ // characters, which may span chunks.
+ int prevChar = -1;
+
+ for (;;)
+ {
+ DWORD bytesRead = 0;
+ BOOL readResult = ReadFile(file, buffer, kChunkSize, &bytesRead, nullptr);
+
+ if (!readResult || GetLastError() == ERROR_BROKEN_PIPE)
+ {
+ break;
+ }
+
+ // walk the buffer and rewrite to eliminate '\r' '\n' pairs
+ char* readCursor = buffer;
+ char const* end = buffer + bytesRead;
+ char* writeCursor = buffer;
+
+ while (readCursor != end)
+ {
+ int p = prevChar;
+ int c = *readCursor++;
+ prevChar = c;
+ switch (c)
+ {
+ case '\r': case '\n':
+ // swallow input if '\r' and '\n' appear in sequence
+ if ((p ^ c) == ('\r' ^ '\n'))
+ {
+ // but don't swallow the next byte
+ prevChar = -1;
+ continue;
+ }
+ // always replace '\r' with '\n'
+ c = '\n';
+ break;
+
+ default:
+ break;
+ }
+
+ *writeCursor++ = c;
+ }
+ bytesRead = (DWORD)(writeCursor - buffer);
+
+ // Note: Current Slang CoreLib gives no way to know
+ // the length of the buffer, so we ultimately have
+ // to just assume null termination...
+ outputBuilder.Append(buffer, bytesRead);
+ }
+
+ info->output = outputBuilder.ProduceString();
+
+ return 0;
+}
+
+void OSProcessSpawner::pushExecutableName(
+ CoreLib::Basic::String executableName)
+{
+ executableName_ = executableName;
+ commandLine_.Append(executableName);
+}
+
+void OSProcessSpawner::pushArgument(
+ CoreLib::Basic::String argument)
+{
+ // TODO(tfoley): handle cases where arguments need some escaping
+ commandLine_.Append(" ");
+ commandLine_.Append(argument);
+}
+
+OSError OSProcessSpawner::spawnAndWaitForCompletion()
+{
+ SECURITY_ATTRIBUTES securityAttributes;
+ securityAttributes.nLength = sizeof(securityAttributes);
+ securityAttributes.lpSecurityDescriptor = nullptr;
+ securityAttributes.bInheritHandle = true;
+
+ // create stdout pipe for child process
+ HANDLE childStdOutReadTmp = nullptr;
+ HANDLE childStdOutWrite = nullptr;
+ if (!CreatePipe(&childStdOutReadTmp, &childStdOutWrite, &securityAttributes, 0))
+ {
+ return kOSError_OperationFailed;
+ }
+
+ // create stderr pipe for child process
+ HANDLE childStdErrReadTmp = nullptr;
+ HANDLE childStdErrWrite = nullptr;
+ if (!CreatePipe(&childStdErrReadTmp, &childStdErrWrite, &securityAttributes, 0))
+ {
+ return kOSError_OperationFailed;
+ }
+
+ // create stdin pipe for child process
+ HANDLE childStdInRead = nullptr;
+ HANDLE childStdInWriteTmp = nullptr;
+ if (!CreatePipe(&childStdInRead, &childStdInWriteTmp, &securityAttributes, 0))
+ {
+ return kOSError_OperationFailed;
+ }
+
+ HANDLE currentProcess = GetCurrentProcess();
+
+ // create a non-inheritable duplicate of the stdout reader
+ HANDLE childStdOutRead = nullptr;
+ if (!DuplicateHandle(
+ currentProcess, childStdOutReadTmp,
+ currentProcess, &childStdOutRead,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ return kOSError_OperationFailed;
+ }
+ if (!CloseHandle(childStdOutReadTmp))
+ {
+ return kOSError_OperationFailed;
+ }
+
+ // create a non-inheritable duplicate of the stderr reader
+ HANDLE childStdErrRead = nullptr;
+ if (!DuplicateHandle(
+ currentProcess, childStdErrReadTmp,
+ currentProcess, &childStdErrRead,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ return kOSError_OperationFailed;
+ }
+ if (!CloseHandle(childStdErrReadTmp))
+ {
+ return kOSError_OperationFailed;
+ }
+
+ // create a non-inheritable duplicate of the stdin writer
+ HANDLE childStdInWrite = nullptr;
+ if (!DuplicateHandle(
+ currentProcess, childStdInWriteTmp,
+ currentProcess, &childStdInWrite,
+ 0, FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ return kOSError_OperationFailed;
+ }
+ if (!CloseHandle(childStdInWriteTmp))
+ {
+ return kOSError_OperationFailed;
+ }
+
+ // Now we can actually get around to starting a process
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo, sizeof(processInfo));
+
+ // TODO: switch to proper wide-character versions of these...
+ STARTUPINFOW startupInfo;
+ ZeroMemory(&startupInfo, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+ startupInfo.hStdError = childStdErrWrite;
+ startupInfo.hStdOutput = childStdOutWrite;
+ startupInfo.hStdInput = childStdInRead;
+ startupInfo.dwFlags = STARTF_USESTDHANDLES;
+
+ // `CreateProcess` requires write access to this, for some reason...
+ BOOL success = CreateProcessW(
+ executableName_.ToWString(),
+ (LPWSTR)commandLine_.ToString().ToWString(),
+ nullptr,
+ nullptr,
+ true,
+ CREATE_NO_WINDOW,
+ nullptr, // TODO: allow specifying environment variables?
+ nullptr,
+ &startupInfo,
+ &processInfo);
+ if (!success)
+ {
+ return kOSError_OperationFailed;
+ }
+
+ // close handles we are now done with
+ CloseHandle(processInfo.hThread);
+ CloseHandle(childStdOutWrite);
+ CloseHandle(childStdErrWrite);
+ CloseHandle(childStdInRead);
+
+ // Create a thread to read from the child's stdout.
+ OSProcessSpawner_ReaderThreadInfo stdOutThreadInfo;
+ stdOutThreadInfo.file = childStdOutRead;
+ HANDLE stdOutThread = CreateThread(nullptr, 0, &osReaderThreadProc, (LPVOID)&stdOutThreadInfo, 0, nullptr);
+
+ // Create a thread to read from the child's stderr.
+ OSProcessSpawner_ReaderThreadInfo stdErrThreadInfo;
+ stdErrThreadInfo.file = childStdErrRead;
+ HANDLE stdErrThread = CreateThread(nullptr, 0, &osReaderThreadProc, (LPVOID)&stdErrThreadInfo, 0, nullptr);
+
+ // wait for the process to exit
+ // TODO: set a timeout as a safety measure...
+ WaitForSingleObject(processInfo.hProcess, INFINITE);
+
+ // get exit code for process
+ DWORD childExitCode = 0;
+ if (!GetExitCodeProcess(processInfo.hProcess, &childExitCode))
+ {
+ return kOSError_OperationFailed;
+ }
+
+ // wait for the reader threads
+ WaitForSingleObject(stdOutThread, INFINITE);
+ WaitForSingleObject(stdErrThread, INFINITE);
+
+ CloseHandle(processInfo.hProcess);
+ CloseHandle(childStdOutRead);
+ CloseHandle(childStdErrRead);
+ CloseHandle(childStdInWrite);
+
+ standardOutput_ = stdOutThreadInfo.output;
+ standardError_ = stdErrThreadInfo.output;
+ resultCode_ = childExitCode;
+
+ return kOSError_None;
+}
+
+#else
+
+// TODO(tfoley): write a default POSIX implementation
+
+#endif
diff --git a/tools/slang-test/os.h b/tools/slang-test/os.h
new file mode 100644
index 000000000..2996001e7
--- /dev/null
+++ b/tools/slang-test/os.h
@@ -0,0 +1,160 @@
+// os.h
+
+#include "../../source/core/slang-io.h"
+
+// This file encapsulates the platform-specific operations needed by the test
+// runner that are not already provided by the core Slang libs
+
+#ifdef _WIN32
+
+// Include Windows header in a way that minimized namespace pollution.
+// TODO: We could try to avoid including this at all, but it would
+// mean trying to hide certain struct layouts, which would add
+// more dynamic allocation.
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <Windows.h>
+#undef WIN32_LEAN_AND_MEAN
+#undef NOMINMAX
+
+#else
+#endif
+
+// A simple set of error codes for possible runtime failures
+enum OSError
+{
+ kOSError_None = 0,
+ kOSError_InvalidArgument,
+ kOSError_OperationFailed,
+ kOSError_FileNotFound,
+};
+
+// A helper type used during enumeration of files in a directory.
+struct OSFindFilesResult
+{
+ CoreLib::Basic::String directoryPath_;
+ CoreLib::Basic::String filePath_;
+#ifdef WIN32
+ HANDLE findHandle_;
+ WIN32_FIND_DATAW fileData_;
+ DWORD requiredMask_;
+ DWORD disallowedMask_;
+ OSError error_;
+#else
+#endif
+
+ bool findNextFile();
+
+ struct Iterator
+ {
+ OSFindFilesResult* context_;
+
+ bool operator!=(Iterator other) const { return context_ != other.context_; }
+ void operator++()
+ {
+ if (!context_->findNextFile())
+ {
+ context_ = NULL;
+ }
+ }
+ CoreLib::Basic::String const& operator*() const
+ {
+ return context_->filePath_;
+ }
+ };
+
+ Iterator begin()
+ {
+#ifdef WIN32
+ Iterator result = { findHandle_ ? this : NULL };
+#else
+#endif
+ return result;
+ }
+
+ Iterator end()
+ {
+ Iterator result = { NULL };
+ return result;
+ }
+};
+
+// Enumerate subdirectories in the given `directoryPath` and return a logical
+// collection of the results that can be iterated with a range-based
+// `for` loop:
+//
+// for( auto subdir : osFindChildDirectories(dir))
+// { ... }
+//
+// Each element in the range is a `CoreLib::Basic::String` representing the
+// path to a subdirecotry of the directory.
+OSFindFilesResult osFindChildDirectories(
+ CoreLib::Basic::String directoryPath);
+
+// Enumerate files in the given `directoryPath` that match the provided
+// `pattern` as a simplified regex for files to return (e.g., "*.txt")
+// and return a logical collection of the results
+// that can be iterated with a range-based `for` loop:
+//
+// for( auto file : osFindFilesInDirectoryMatchingPattern(dir, "*.txt"))
+// { ... }
+//
+// Each element in the range is a `CoreLib::Basic::String` representing the
+// path to a file in the directory.
+OSFindFilesResult osFindFilesInDirectoryMatchingPattern(
+ CoreLib::Basic::String directoryPath,
+ CoreLib::Basic::String pattern);
+
+// Enumerate files in the given `directoryPath` and return a logical
+// collection of the results that can be iterated with a range-based
+// `for` loop:
+//
+// for( auto file : osFindFilesInDirectory(dir))
+// { ... }
+//
+// Each element in the range is a `CoreLib::Basic::String` representing the
+// path to a file in the directory.
+OSFindFilesResult osFindFilesInDirectory(
+ CoreLib::Basic::String directoryPath);
+
+
+// An `OSProcessSpawner` can be used to launch a process, and handles
+// putting together the arguments in the form required by the target
+// platform, as well as capturing any output from the process (both
+// standard output and standard error) as strings.
+struct OSProcessSpawner
+{
+ // Set the executable name for the process to be spawned.
+ // Note: this call must be made before any arguments are pushed.
+ void pushExecutableName(
+ CoreLib::Basic::String executableName);
+
+ // Append an argument for the process to be spawned.
+ void pushArgument(
+ CoreLib::Basic::String argument);
+
+ // Attempt to spawn the process, and wait for it to complete.
+ // Returns an error if the attempt to spawn and/or wait fails,
+ // but returns `kOSError_None` if the process is run to completion,
+ // whether or not the process returns "successfully" (with a zero
+ // result code);
+ OSError spawnAndWaitForCompletion();
+
+ // If the process is successfully spawned and completes, then
+ // the user can query the result code that the process produce
+ // on exit, along with the output it wrote to stdout and stderr.
+ typedef int ResultCode;
+ ResultCode getResultCode() { return resultCode_; }
+ CoreLib::Basic::String const& getStandardOutput() { return standardOutput_; }
+ CoreLib::Basic::String const& getStandardError() { return standardError_; }
+
+ // "private" data follows
+ CoreLib::Basic::String standardOutput_;
+ CoreLib::Basic::String standardError_;
+ ResultCode resultCode_;
+#ifdef WIN32
+ CoreLib::Basic::String executableName_;
+ CoreLib::Basic::StringBuilder commandLine_;
+#else
+#endif
+};
diff --git a/tools/slang-test/slang-test.vcxproj b/tools/slang-test/slang-test.vcxproj
new file mode 100644
index 000000000..b8d23d569
--- /dev/null
+++ b/tools/slang-test/slang-test.vcxproj
@@ -0,0 +1,277 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug_VS2013|Win32">
+ <Configuration>Debug_VS2013</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug_VS2013|x64">
+ <Configuration>Debug_VS2013</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release_VS2013|Win32">
+ <Configuration>Release_VS2013</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release_VS2013|x64">
+ <Configuration>Release_VS2013</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{0C768A18-1D25-4000-9F37-DA5FE99E3B64}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>SpireTestTool</RootNamespace>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v120</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug_VS2013|Win32'">
+ <PlatformToolset>v140</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug_VS2013|x64'">
+ <PlatformToolset>v120</PlatformToolset>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_VS2013|Win32'">
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_VS2013|x64'">
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_VS2013|Win32'">
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_VS2013|x64'">
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_VS2013|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_VS2013|x64'">
+ <ClCompile>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <Optimization>Disabled</Optimization>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="os.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="os.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\core\core.vcxproj">
+ <Project>{f9be7957-8399-899e-0c49-e714fddd4b65}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/tools/slang-test/slang-test.vcxproj.filters b/tools/slang-test/slang-test.vcxproj.filters
new file mode 100644
index 000000000..3549e6046
--- /dev/null
+++ b/tools/slang-test/slang-test.vcxproj.filters
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="os.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="os.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file