summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/visual-studio/slang-test/slang-test.vcxproj1
-rw-r--r--build/visual-studio/slang-test/slang-test.vcxproj.filters3
-rw-r--r--deps/target-deps.json10
-rw-r--r--source/compiler-core/slang-artifact.h5
-rw-r--r--source/compiler-core/slang-downstream-compiler.h5
-rw-r--r--tests/autodiff/cuda-kernel-export-2.slang7
-rw-r--r--tests/autodiff/cuda-kernel-export.slang14
-rw-r--r--tests/bugs/gh-449.slang7
-rw-r--r--tests/bugs/gh-449.slang.expected4
-rw-r--r--tests/bugs/gh-841.slang14
-rw-r--r--tests/compute/simple.slang6
-rw-r--r--tests/diagnostics/local-line.slang2
-rw-r--r--tests/diagnostics/local-line.slang.expected2
-rw-r--r--tools/slang-test/filecheck.h30
-rw-r--r--tools/slang-test/slang-test-main.cpp604
-rw-r--r--tools/slang-test/test-context.cpp23
-rw-r--r--tools/slang-test/test-context.h8
-rw-r--r--tools/slang-test/test-reporter.h1
18 files changed, 466 insertions, 280 deletions
diff --git a/build/visual-studio/slang-test/slang-test.vcxproj b/build/visual-studio/slang-test/slang-test.vcxproj
index 1dc703860..ae586ca9d 100644
--- a/build/visual-studio/slang-test/slang-test.vcxproj
+++ b/build/visual-studio/slang-test/slang-test.vcxproj
@@ -278,6 +278,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\..\tools\slang-test\directory-util.h" />
+ <ClInclude Include="..\..\..\tools\slang-test\filecheck.h" />
<ClInclude Include="..\..\..\tools\slang-test\options.h" />
<ClInclude Include="..\..\..\tools\slang-test\parse-diagnostic-util.h" />
<ClInclude Include="..\..\..\tools\slang-test\slangc-tool.h" />
diff --git a/build/visual-studio/slang-test/slang-test.vcxproj.filters b/build/visual-studio/slang-test/slang-test.vcxproj.filters
index 022b9af6d..96d273897 100644
--- a/build/visual-studio/slang-test/slang-test.vcxproj.filters
+++ b/build/visual-studio/slang-test/slang-test.vcxproj.filters
@@ -12,6 +12,9 @@
<ClInclude Include="..\..\..\tools\slang-test\directory-util.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\tools\slang-test\filecheck.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\tools\slang-test\options.h">
<Filter>Header Files</Filter>
</ClInclude>
diff --git a/deps/target-deps.json b/deps/target-deps.json
index ba1fc700d..05dc2e7fb 100644
--- a/deps/target-deps.json
+++ b/deps/target-deps.json
@@ -4,14 +4,14 @@
"dependencies" : [
{
"name" : "slang-llvm",
- "baseUrl" : "https://github.com/shader-slang/slang-llvm/releases/download/v13.x-36/",
+ "baseUrl" : "https://github.com/shader-slang/slang-llvm/releases/download/v13.x-37/",
"optional" : true,
"packages" :
{
- "windows-x86_64" : { "type" : "url", "path" : "slang-llvm-13.x-36-win64.zip" },
- "windows-x86" : { "type": "url", "path" : "slang-llvm-13.x-36-win32.zip" },
- "linux-x86_64" : { "type": "url", "path" : "slang-llvm-v13.x-36-linux-x86_64-release.zip" },
- "macosx-x86_64" : { "type": "url", "path" : "slang-llvm-v13.x-36-macosx-x86_64-release.zip" }
+ "windows-x86_64" : { "type" : "url", "path" : "slang-llvm-13.x-37-win64.zip" },
+ "windows-x86" : { "type": "url", "path" : "slang-llvm-13.x-37-win32.zip" },
+ "linux-x86_64" : { "type": "url", "path" : "slang-llvm-v13.x-37-linux-x86_64-release.zip" },
+ "macosx-x86_64" : { "type": "url", "path" : "slang-llvm-v13.x-37-macosx-x86_64-release.zip" }
}
},
{
diff --git a/source/compiler-core/slang-artifact.h b/source/compiler-core/slang-artifact.h
index 80db717d0..6072cdf76 100644
--- a/source/compiler-core/slang-artifact.h
+++ b/source/compiler-core/slang-artifact.h
@@ -5,6 +5,8 @@
#include "../core/slang-basic.h"
#include "../../slang-com-helper.h"
+#include <type_traits>
+
namespace Slang
{
@@ -47,7 +49,9 @@ struct CharSlice : public Slice<char>
explicit CharSlice(const char* in) :Super(in, ::strlen(in)) {}
CharSlice(const char* in, Count inCount) :Super(in, inCount) {}
CharSlice() :Super(nullptr, 0) {}
+ explicit CharSlice(const String& s) :CharSlice(s.begin(), s.getLength()){};
};
+static_assert(std::is_trivially_copyable_v<CharSlice>);
struct TerminatedCharSlice : public CharSlice
{
@@ -64,6 +68,7 @@ struct TerminatedCharSlice : public CharSlice
TerminatedCharSlice(const char* in, Count inCount) :Super(in, inCount) { SLANG_ASSERT(in[inCount] == 0); }
TerminatedCharSlice() :Super("", 0) {}
};
+static_assert(std::is_trivially_copyable_v<TerminatedCharSlice>);
/* As a rule of thumb, if we can define some aspect in a hierarchy then we should do so at the highest level.
If some aspect can apply to multiple items identically we move that to a separate enum.
diff --git a/source/compiler-core/slang-downstream-compiler.h b/source/compiler-core/slang-downstream-compiler.h
index 19f241ccf..4feffa743 100644
--- a/source/compiler-core/slang-downstream-compiler.h
+++ b/source/compiler-core/slang-downstream-compiler.h
@@ -16,6 +16,8 @@
#include "slang-artifact.h"
#include "slang-artifact-associated.h"
+#include <type_traits>
+
namespace Slang
{
@@ -133,7 +135,7 @@ bool isVersionCompatible(const T& in)
NOTE! This type is trafficed across shared library boundaries and *versioned*.
In particular
-* The struct can only contain types that can be trivially memcpyd.
+* The struct can only contain types that can be trivially memcpyd (checked by static_assert);
* New fields can only be added to the end of the struct
* New fields must take into account alignment/padding such that they do not share bytes in previous version sizes
*/
@@ -256,6 +258,7 @@ struct DownstreamCompileOptions
// The debug info format to use.
SlangDebugInfoFormat m_debugInfoFormat = SLANG_DEBUG_INFO_FORMAT_DEFAULT;
};
+static_assert(std::is_trivially_copyable_v<DownstreamCompileOptions>);
#define SLANG_ALIAS_DEPRECIATED_VERSION(name, id, firstField, lastField) \
struct name##_AliasDepreciated##id \
diff --git a/tests/autodiff/cuda-kernel-export-2.slang b/tests/autodiff/cuda-kernel-export-2.slang
index 9cbb4e881..93abddfa2 100644
--- a/tests/autodiff/cuda-kernel-export-2.slang
+++ b/tests/autodiff/cuda-kernel-export-2.slang
@@ -1,13 +1,13 @@
-//DISABLE_TEST:SIMPLE: -target cuda -line-directive-mode none
+//TEST:SIMPLE(filecheck=CUDA): -target cuda -line-directive-mode none
// Verify that we can output a cuda device function with [CudaDeviceExport].
-// Disabled until we have FileCheck.
//////////////////////////////////////////////////////////////////////////
// Lambda GGX
//////////////////////////////////////////////////////////////////////////
+// CUDA-DAG: __device__ float lambdaGGX(float alphaSqr_[[#]], float cosTheta_[[#]])
[CudaDeviceExport]
[BackwardDifferentiable]
float lambdaGGX(const float alphaSqr, const float cosTheta)
@@ -19,6 +19,7 @@ float lambdaGGX(const float alphaSqr, const float cosTheta)
return 0.5f * (sqrt(1.0f + alphaSqr * tanThetaSqr) - 1.0f);
}
+// CUDA-DAG: __device__ void lambdaGGX_bwd(DiffPair_float_[[#]] * alphaSqr_[[#]], DiffPair_float_[[#]] * cosTheta_[[#]], float d_out_[[#]])
[CudaDeviceExport]
void lambdaGGX_bwd(inout DifferentialPair<float> alphaSqr, inout DifferentialPair<float> cosTheta, const float d_out)
{
@@ -29,6 +30,7 @@ void lambdaGGX_bwd(inout DifferentialPair<float> alphaSqr, inout DifferentialPai
// Masking Smith
//////////////////////////////////////////////////////////////////////////
+// CUDA-DAG: __device__ float maskingSmithGGXCorrelated(float alphaSqr_[[#]], float cosThetaI_[[#]], float cosThetaO_[[#]])
[CudaDeviceExport]
[BackwardDifferentiable]
float maskingSmithGGXCorrelated(const float alphaSqr, const float cosThetaI, const float cosThetaO)
@@ -38,6 +40,7 @@ float maskingSmithGGXCorrelated(const float alphaSqr, const float cosThetaI, con
return 1.0f / (1.0f + lambdaI + lambdaO);
}
+// CUDA-DAG: __device__ void maskingSmithGGXCorrelated_bwd(DiffPair_float_[[#]] * alphaSqr_[[#]], DiffPair_float_[[#]] * cosThetaI_[[#]], DiffPair_float_[[#]] * cosThetaO_[[#]], float d_out_[[#]])
[CudaDeviceExport]
void maskingSmithGGXCorrelated_bwd(inout DifferentialPair<float> alphaSqr,
inout DifferentialPair<float> cosThetaI,
diff --git a/tests/autodiff/cuda-kernel-export.slang b/tests/autodiff/cuda-kernel-export.slang
index e16188abc..928133c94 100644
--- a/tests/autodiff/cuda-kernel-export.slang
+++ b/tests/autodiff/cuda-kernel-export.slang
@@ -1,7 +1,7 @@
-//DISABLE_TEST:SIMPLE: -target cuda -line-directive-mode none
+//TEST:SIMPLE(filecheck=CUDA): -target cuda -line-directive-mode none
+//TEST:SIMPLE(filecheck=TORCH): -target torch -line-directive-mode none
-// Verify that we can output a cuda device function with [CudaDeviceExport].
-// Disabled until we have FileCheck.
+// Verify that we can output a cuda device function with [CudaKernel].
struct MySubType
{
@@ -20,6 +20,7 @@ struct MyInput
float normalVal;
}
+// CUDA: __global__ void myKernel(TensorView inValues_[[#]], TensorView outValues_[[#]])
[CudaKernel]
void myKernel(TensorView<float> inValues, TensorView<float> outValues)
{
@@ -28,6 +29,11 @@ void myKernel(TensorView<float> inValues, TensorView<float> outValues)
outValues.store(cudaThreadIdx().x, sin(inValues.load(cudaThreadIdx().x)));
}
+// TORCH: {{^SLANG_PRELUDE_EXPORT$}}
+// TORCH-NEXT: void myKernel(TensorView {{[[:alnum:]_]+}}, TensorView {{[[:alnum:]_]+}});
+//
+// TORCH: {{^SLANG_PRELUDE_EXPORT$}}
+// TORCH-NEXT: std::tuple<std::tuple<float, float>, std::tuple<std::tuple<std::tuple<torch::Tensor, torch::Tensor>>, std::tuple<std::tuple<torch::Tensor, torch::Tensor>>>> runCompute(std::tuple<torch::Tensor, float> input_[[#]])
[TorchEntryPoint]
public __extern_cpp MyType runCompute(MyInput input)
{
@@ -44,4 +50,4 @@ public __extern_cpp MyType runCompute(MyInput input)
rs.sub[1].array[0] = inValues;
rs.sub[1].array[1] = outValues;
return rs;
-} \ No newline at end of file
+}
diff --git a/tests/bugs/gh-449.slang b/tests/bugs/gh-449.slang
index 9ce678b53..1b7a9f07d 100644
--- a/tests/bugs/gh-449.slang
+++ b/tests/bugs/gh-449.slang
@@ -1,5 +1,6 @@
// gh-449.slang
//TEST:SIMPLE:
+//TEST:SIMPLE(filecheck=CHECK):
// Issue when dealing with binary operations that
// mix scalars with vectors that have a different
@@ -16,10 +17,16 @@ void main()
float2 a = float2(1, 2);
uint b = 3;
foo(a + b);
+ // CHECK: tests/bugs/gh-449.slang([[#@LINE-1]]): error 30019: expected an expression of type 'S', got 'vector<float,2>'
+ // CHECK-NEXT: foo(a + b);
+ // CHECK-NEXT: ^
// This used to get confused, with the `f` getting converted
// to a `uint` before the addition.
uint2 u = uint2(1, 2);
float f = 3.0;
foo(u + f);
+ // CHECK: tests/bugs/gh-449.slang([[#@LINE-1]]): error 30019: expected an expression of type 'S', got 'vector<float,2>'
+ // CHECK-NEXT: foo(u + f);
+ // CHECK-NEXT: ^
}
diff --git a/tests/bugs/gh-449.slang.expected b/tests/bugs/gh-449.slang.expected
index 8ca87a406..4d01e7fe3 100644
--- a/tests/bugs/gh-449.slang.expected
+++ b/tests/bugs/gh-449.slang.expected
@@ -1,9 +1,9 @@
result code = -1
standard error = {
-tests/bugs/gh-449.slang(18): error 30019: expected an expression of type 'S', got 'vector<float,2>'
+tests/bugs/gh-449.slang(19): error 30019: expected an expression of type 'S', got 'vector<float,2>'
foo(a + b);
^
-tests/bugs/gh-449.slang(24): error 30019: expected an expression of type 'S', got 'vector<float,2>'
+tests/bugs/gh-449.slang(28): error 30019: expected an expression of type 'S', got 'vector<float,2>'
foo(u + f);
^
}
diff --git a/tests/bugs/gh-841.slang b/tests/bugs/gh-841.slang
index 44d8348e5..8dc687e77 100644
--- a/tests/bugs/gh-841.slang
+++ b/tests/bugs/gh-841.slang
@@ -1,12 +1,26 @@
// gh-841.slang
//TEST:CROSS_COMPILE: -profile ps_5_0 -entry main -target spirv-assembly
+//TEST:CROSS_COMPILE(filecheck=SPV): -profile ps_5_0 -entry main -target spirv-assembly
+//TEST:CROSS_COMPILE(filecheck=GLSL): -profile ps_5_0 -entry main -target glsl
// GitHub issue #841: failing to emit `flat` modifier in output GLSL when required
struct RasterVertex
{
+ // location 0
float4 c : COLOR;
+
+ // Make sure that the input value in location 1 is decorated as Flat
+ // SPV-DAG: [[#VAL:]]{{.*}}:{{.*}} Variable Input
+ // SPV-DAG: Decorate [[#VAL]]{{.*}} Location 1
+ // SPV-DAG: Decorate [[#VAL]]{{.*}} Flat
+ //
+ // Likewise for GLSL
+ // GLSL: flat layout(location = 1)
+ // GLSL-NEXT: in uint {{[[:alnum:]_]+}};
+ //
+ // location 1
uint u : FLAGS;
};
diff --git a/tests/compute/simple.slang b/tests/compute/simple.slang
index c62f88d5e..110a590a1 100644
--- a/tests/compute/simple.slang
+++ b/tests/compute/simple.slang
@@ -1,5 +1,11 @@
//TEST(smoke,compute):COMPARE_COMPUTE: -shaderobj
//TEST(smoke,compute):COMPARE_COMPUTE:-cpu -shaderobj
+//TEST(smoke,compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -shaderobj
+
+// CHECK: 0
+// CHECK-NEXT: 3F800000
+// CHECK-NEXT: 40000000
+// CHECK-NEXT: 40400000
// This is a basic test for Slang compute shader.
diff --git a/tests/diagnostics/local-line.slang b/tests/diagnostics/local-line.slang
index 973c234c0..532307a8f 100644
--- a/tests/diagnostics/local-line.slang
+++ b/tests/diagnostics/local-line.slang
@@ -3,6 +3,7 @@
//TEST:SIMPLE_LINE:-entry computeMain -target dxbc -stage compute
//TEST:SIMPLE_LINE:-entry computeMain -target dll -stage compute
//TEST:SIMPLE_LINE:-entry computeMain -target ptx -stage compute
+//TEST:SIMPLE_LINE(filecheck=CHECK):-entry computeMain -target spirv -stage compute
//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name outputBuffer
RWStructuredBuffer<int> outputBuffer;
@@ -37,6 +38,7 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
else
{
a = e; b = c + c; d += d + __SyntaxError(); e = doThing(e, int(dispatchThreadID.x));
+ // CHECK: [[#@LINE-1]]
}
}
diff --git a/tests/diagnostics/local-line.slang.expected b/tests/diagnostics/local-line.slang.expected
index a2720097d..425151f3a 100644
--- a/tests/diagnostics/local-line.slang.expected
+++ b/tests/diagnostics/local-line.slang.expected
@@ -1 +1 @@
-39
+40
diff --git a/tools/slang-test/filecheck.h b/tools/slang-test/filecheck.h
new file mode 100644
index 000000000..4f527bf14
--- /dev/null
+++ b/tools/slang-test/filecheck.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "../../source/core/slang-common.h"
+#include "../../source/compiler-core/slang-artifact.h"
+#include "../../tools/unit-test/slang-unit-test.h"
+
+namespace Slang
+{
+
+class IFileCheck : public ICastable
+{
+public:
+ SLANG_COM_INTERFACE( 0x046bfe4a, 0x99a3, 0x402f, {0x83, 0xd7, 0x81, 0x8d, 0xa1, 0x38, 0xed, 0xfa})
+
+ using ReportDiagnostic = void (SLANG_STDCALL *)(void*, TestMessageType, const char*) noexcept;
+
+ virtual TestResult SLANG_MCALL performTest(
+ const char* programName, // Included in diagnostic messages, for example "slang-test"
+ const char* rulesFilePath, // The file from which to read the FileCheck rules
+ const char* fileCheckPrefix, // The name of the FileCheck files to use in the rules file
+ const char* stringToCheck, // The string to match with the rules
+ const char* stringToCheckName, // The name of that string, for example "actual-output"
+ ReportDiagnostic testReporter, // A callback for reporting diagnostic messages
+ void* reporterData, // Some data to pass on to the callback
+ bool colorDiagnosticOutput // Include color control codes in the string passed to testReporter
+ ) noexcept = 0;
+};
+
+}
+
diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp
index e7d2db94d..410250aa0 100644
--- a/tools/slang-test/slang-test-main.cpp
+++ b/tools/slang-test/slang-test-main.cpp
@@ -79,11 +79,23 @@ struct TestOptions
}
}
+ // Small helper to help consistently interrogating for filecheck usage
+ bool getFileCheckPrefix(String& prefix) const
+ {
+ return commandOptions.TryGetValue("filecheck", prefix);
+ }
+ bool getFileCheckBufferPrefix(String& prefix) const
+ {
+ return commandOptions.TryGetValue("filecheck-buffer", prefix);
+ }
+
Type type = Type::Normal;
String command;
List<String> args;
+ Dictionary<String, String> commandOptions;
+
// The categories that this test was assigned to
List<TestCategory*> categories;
@@ -136,6 +148,24 @@ static void _addRenderTestOptions(const Options& options, CommandLine& cmdLine);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! Functions !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+// Tries to read in order
+// - The file specific to this test: input.outputStem + suffix
+// - The general file for a set of tests: input.fileName + suffix;
+static SlangResult _readTestFile(const TestInput& input, const String& suffix, String& out)
+{
+ StringBuilder buf;
+ buf << input.outputStem << suffix;
+ if(auto r = Slang::File::readAllText(buf, out); SLANG_SUCCEEDED(r))
+ {
+ return r;
+ }
+
+ buf.Clear();
+ buf << input.filePath << suffix;
+ return Slang::File::readAllText(buf, out);
+}
+
+
bool match(char const** ioCursor, char const* expected)
{
char const* cursor = *ioCursor;
@@ -219,7 +249,7 @@ String collectRestOfLine(char const** ioCursor)
return getString(textBegin, textEnd);
}
-static bool _isEndOfCategoryList(char c)
+static bool _isEndOfLineOrParens(char c)
{
switch (c)
{
@@ -245,7 +275,7 @@ static SlangResult _parseCategories(TestCategorySet* categorySet, char const** i
const char*const start = cursor;
// Find the end
- for (; !_isEndOfCategoryList(*cursor); ++cursor);
+ for (; !_isEndOfLineOrParens(*cursor); ++cursor);
if (*cursor != ')')
{
*ioCursor = cursor;
@@ -278,6 +308,46 @@ static SlangResult _parseCategories(TestCategorySet* categorySet, char const** i
return SLANG_OK;
}
+static SlangResult _parseCommandArguments(char const** ioCursor, TestOptions& out)
+{
+ char const* cursor = *ioCursor;
+
+ // If don't have ( we don't have any additional options
+ if (*cursor == '(')
+ {
+ cursor++;
+ const char*const start = cursor;
+
+ // Find the end
+ for (; !_isEndOfLineOrParens(*cursor); ++cursor);
+ if (*cursor != ')')
+ {
+ *ioCursor = cursor;
+ return SLANG_FAIL;
+ }
+ cursor++;
+
+ List<UnownedStringSlice> options;
+ StringUtil::split(UnownedStringSlice(start, cursor - 1), ',', options);
+
+ for (auto& option : options)
+ {
+ auto i = option.indexOf('=');
+ if(i == -1)
+ {
+ out.commandOptions.Add(option.trim(), "");
+ }
+ else
+ {
+ out.commandOptions.Add(option.head(i).trim(), option.tail(i+1).trim());
+ }
+ }
+ }
+
+ *ioCursor = cursor;
+ return SLANG_OK;
+}
+
static SlangResult _parseArg(const char** ioCursor, UnownedStringSlice& outArg)
{
const char* cursor = *ioCursor;
@@ -342,6 +412,7 @@ static SlangResult _gatherTestOptions(
cursor++;
continue;
+ case '(':
case ':':
break;
@@ -355,6 +426,13 @@ static SlangResult _gatherTestOptions(
outOptions.command = getString(commandStart, commandEnd);
+ // Allow parameterizing the test command separately from the arguments, this
+ // is because the arguments are often passed to the compiler verbatim, and
+ // it's messy to have the test runner rifling through and picking things
+ // out
+ // Format is: (foo=bar, baz = 2)
+ SLANG_RETURN_ON_FAIL(_parseCommandArguments(&cursor, outOptions));
+
if(*cursor == ':')
cursor++;
else
@@ -537,7 +615,115 @@ static SlangResult _gatherTestsForFile(
return SLANG_OK;
}
+static void SLANG_STDCALL _fileCheckDiagnosticCallback(void* data, const TestMessageType messageType, const char* message) noexcept
+{
+ auto& testReporter = *reinterpret_cast<TestReporter*>(data);
+ testReporter.message(messageType, message);
+}
+
+//
+// Check some generated output with FileCheck
+//
+static TestResult _fileCheckTest(
+ TestContext& context,
+ const String& fileCheckRules,
+ const String& fileCheckPrefix,
+ const String& outputToCheck)
+{
+ auto& testReporter = *context.getTestReporter();
+
+ IFileCheck* fc = context.getFileCheck();
+ if(!fc)
+ {
+ testReporter.message(TestMessageType::RunError, "FileCheck is not available");
+ return TestResult::Fail;
+ }
+
+ const bool coloredOutput = true;
+ return fc->performTest(
+ "slang-test",
+ fileCheckRules.begin(),
+ fileCheckPrefix.begin(),
+ outputToCheck.begin(),
+ "actual-output",
+ _fileCheckDiagnosticCallback,
+ &testReporter,
+ coloredOutput);
+}
+
+template<typename Compare>
+static TestResult _fileComparisonTest(
+ TestContext& context,
+ const TestInput& input,
+ const char* defaultExpectedContent,
+ const char* expectedFileSuffix,
+ const String& actualOutput,
+ Compare compare)
+{
+ String expectedOutput;
+ if (SLANG_FAILED(_readTestFile(input, expectedFileSuffix, expectedOutput)))
+ {
+ if(defaultExpectedContent)
+ {
+ expectedOutput = defaultExpectedContent;
+ }
+ else
+ {
+ context.getTestReporter()->messageFormat(TestMessageType::RunError,
+ "Unable to read %s output for '%s'\n",
+ expectedFileSuffix,
+ input.outputStem.getBuffer());
+ return TestResult::Fail;
+ }
+ }
+
+ // Otherwise we compare to the expected output
+ if (!compare(actualOutput, expectedOutput))
+ {
+ context.getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
+ return TestResult::Fail;
+ }
+ return TestResult::Pass;
+}
+
+static bool _areLinesEqual(const String& a, const String& e)
+{
+ return StringUtil::areLinesEqual(a.getUnownedSlice(), e.getUnownedSlice());
+}
+
+// Either run FileCheck over the result, or read and compare with a .expected file
+// On a comparison failure, dump the difference
+// On any failure, write a .actual file.
+template<typename Compare = decltype(_areLinesEqual)>
+static TestResult _validateOutput(
+ TestContext* const context,
+ const TestInput& input,
+ const String& actualOutput,
+ const bool forceFailure = false,
+ const char* defaultExpectedContent = nullptr,
+ const Compare compare = _areLinesEqual)
+{
+ String fileCheckPrefix;
+ const TestResult result =
+ input.testOptions->getFileCheckPrefix(fileCheckPrefix)
+ ? _fileCheckTest(*context, input.filePath, fileCheckPrefix, actualOutput)
+ : _fileComparisonTest(*context, input, defaultExpectedContent, ".expected", actualOutput, compare);
+
+ // 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 == TestResult::Fail || forceFailure)
+ {
+ String actualOutputPath = input.outputStem + ".actual";
+ Slang::File::writeAllText(actualOutputPath, actualOutput);
+ return TestResult::Fail;
+ }
+ else
+ {
+ return result;
+ }
+}
Result spawnAndWaitExe(TestContext* context, const String& testPath, const CommandLine& cmdLine, ExecuteResult& outRes)
{
@@ -1170,7 +1356,7 @@ String findExpectedPath(const TestInput& input, const char* postFix)
}
// Couldn't find either
- printf("referenceOutput '%s' or '%s' not found.\n", defaultBuf.getBuffer(), specializedBuf.getBuffer());
+ fprintf(stderr, "referenceOutput '%s' or '%s' not found.\n", defaultBuf.getBuffer(), specializedBuf.getBuffer());
return "";
}
@@ -1863,72 +2049,13 @@ TestResult runSimpleTest(TestContext* context, TestInput& input)
String actualOutput = getOutput(exeRes);
- String expectedOutputPath = outputStem + ".expected";
- String expectedOutput;
-
- Slang::File::readAllText(expectedOutputPath, expectedOutput);
-
- // If no expected output file was found, then we
- // expect everything to be empty
- if (expectedOutput.getLength() == 0)
- {
- expectedOutput = "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n";
- }
-
- TestResult result = TestResult::Pass;
-
- // Otherwise we compare to the expected output
- if (!_areResultsEqual(input.testOptions->type, expectedOutput, actualOutput))
- {
- context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
- result = TestResult::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 == TestResult::Fail)
- {
- String actualOutputPath = outputStem + ".actual";
- Slang::File::writeAllText(actualOutputPath, actualOutput);
-
- context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
- }
-
- return result;
-}
-
-SlangResult _readText(const UnownedStringSlice& path, String& out)
-{
- return Slang::File::readAllText(path, out);
-}
-
-static SlangResult _readExpected(const UnownedStringSlice& stem, String& out)
-{
- StringBuilder buf;
-
- // See if we have a trailing . index, and try *without* that first
- const Index dotIndex = stem.lastIndexOf('.');
- if (dotIndex >= 0)
- {
- const UnownedStringSlice postfix = stem.tail(dotIndex + 1);
-
- Int value;
- if (SLANG_SUCCEEDED(StringUtil::parseInt(postfix, value)))
- {
- UnownedStringSlice head = stem.head(dotIndex);
-
- buf << head << ".expected";
-
- if (SLANG_SUCCEEDED(_readText(buf.getUnownedSlice(), out)))
- {
- return SLANG_OK;
- }
- }
- }
-
- buf << stem << ".expected";
- return _readText(buf.getUnownedSlice(), out);
+ return _validateOutput(
+ context,
+ input,
+ actualOutput,
+ false,
+ "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n",
+ [&input](auto e, auto a){return _areResultsEqual(input.testOptions->type, e, a);});
}
TestResult runSimpleLineTest(TestContext* context, TestInput& input)
@@ -1977,38 +2104,7 @@ TestResult runSimpleLineTest(TestContext* context, TestInput& input)
actualOutput << "No output diagnostics\n";
}
- TestResult result = TestResult::Fail;
-
- String expectedOutput;
-
- if (SLANG_SUCCEEDED(_readExpected(outputStem.getUnownedSlice(), expectedOutput)))
- {
- if (StringUtil::areLinesEqual(expectedOutput.getUnownedSlice(), actualOutput.getUnownedSlice()))
- {
- result = TestResult::Pass;
- }
- else
- {
- context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
- }
- }
- else
- {
- StringBuilder buf;
- buf << "Unable to find expected output for '" << outputStem << "'";
- context->getTestReporter()->message(TestMessageType::TestFailure, buf);
- }
-
- // 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 == TestResult::Fail)
- {
- String actualOutputPath = outputStem + ".actual";
- Slang::File::writeAllText(actualOutputPath, actualOutput);
- }
-
- return result;
+ return _validateOutput(context, input, actualOutput, false);
}
TestResult runCompile(TestContext* context, TestInput& input)
@@ -2071,8 +2167,8 @@ TestResult runSimpleCompareCommandLineTest(TestContext* context, TestInput& inpu
TestResult runReflectionTest(TestContext* context, TestInput& input)
{
const auto& options = context->options;
- auto filePath = input.filePath;
- auto outputStem = input.outputStem;
+ const auto& filePath = input.filePath;
+ auto& outputStem = input.outputStem;
bool isCPUTest = input.testOptions->command.startsWith("CPU_");
@@ -2105,55 +2201,7 @@ TestResult runReflectionTest(TestContext* context, TestInput& input)
#endif
}
- String expectedOutputPath = outputStem + ".expected";
- String expectedOutput;
-
- Slang::File::readAllText(expectedOutputPath, expectedOutput);
-
- // If no expected output file was found, then we
- // expect everything to be empty
- if (expectedOutput.getLength() == 0)
- {
- expectedOutput = "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n";
- }
-
- TestResult result = TestResult::Pass;
-
- // Otherwise we compare to the expected output
- if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
- {
- result = TestResult::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 == TestResult::Fail)
- {
- String actualOutputPath = outputStem + ".actual";
- Slang::File::writeAllText(actualOutputPath, actualOutput);
-
- context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
- }
-
- return result;
-}
-
-String getExpectedOutput(String const& outputStem)
-{
- String expectedOutputPath = outputStem + ".expected";
- String expectedOutput;
-
- Slang::File::readAllText(expectedOutputPath, expectedOutput);
-
- // If no expected output file was found, then we
- // expect everything to be empty
- if (expectedOutput.getLength() == 0)
- {
- expectedOutput = "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n";
- }
-
- return expectedOutput;
+ return _validateOutput(context, input, actualOutput);
}
static String _calcSummary(IArtifactDiagnostics* inDiagnostics)
@@ -2462,23 +2510,18 @@ static TestResult runCPPCompilerExecute(TestContext* context, TestInput& input)
return TestResult::Pass;
}
-TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
+// Returns TestResult::Ignored if we don't have the capability to run the passthrough compiler
+// Returns TestResult::Fail if we can't write the expected output debug file
+// Otherwise return TestResult::Pass and if we are not just collecting
+// requirements, writes the output into the `expectedOutput` parameter
+static TestResult generateExpectedOutput(TestContext* const context, const TestInput& input, String& expectedOutput)
{
- // need to execute the stand-alone Slang compiler on the file
- // then on the same file + `.glsl` and compare output
-
auto filePath = input.filePath;
auto outputStem = input.outputStem;
- CommandLine actualCmdLine;
CommandLine expectedCmdLine;
- _initSlangCompiler(context, actualCmdLine);
_initSlangCompiler(context, expectedCmdLine);
-
- actualCmdLine.addArg(filePath);
-
- // TODO(JS): This should no longer be needed with TestInfo accumulated for a test
const auto& args = input.testOptions->args;
@@ -2520,44 +2563,60 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
}
}
}
-
- for( auto arg : input.testOptions->args )
+
+ for (auto arg : args)
{
- actualCmdLine.addArg(arg);
expectedCmdLine.addArg(arg);
}
ExecuteResult expectedExeRes;
TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, expectedCmdLine, expectedExeRes));
- String expectedOutput;
- if (context->isExecuting())
+ if (context->isCollectingRequirements())
{
- expectedOutput = getOutput(expectedExeRes);
- String expectedOutputPath = outputStem + ".expected";
-
- if (SLANG_FAILED(Slang::File::writeAllText(expectedOutputPath, expectedOutput)))
- {
- return TestResult::Fail;
- }
+ return TestResult::Pass;
}
- ExecuteResult actualExeRes;
- TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, actualCmdLine, actualExeRes));
+ expectedOutput = getOutput(expectedExeRes);
+ String expectedOutputPath = outputStem + ".expected";
- if (context->isCollectingRequirements())
+ if (SLANG_FAILED(Slang::File::writeAllText(expectedOutputPath, expectedOutput)))
{
- return TestResult::Pass;
+ context->getTestReporter()->messageFormat(
+ TestMessageType::TestFailure,
+ "Failed to write test expected output to %s",
+ expectedOutputPath.getBuffer());
+ return TestResult::Fail;
}
- String actualOutput = getOutput(actualExeRes);
+ return TestResult::Pass;
+}
- TestResult result = TestResult::Pass;
+// Returns TestResult::Fail if compilation fails
+// Otherwise return TestResult::Pass and if we are not just collecting
+// requirements, writes the output into the `expectedOutput` parameter
+TestResult generateActualOutput(TestContext* const context, const TestInput& input, String& actualOutput)
+{
+ auto filePath = input.filePath;
- // Otherwise we compare to the expected output
- if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
+ CommandLine actualCmdLine;
+ _initSlangCompiler(context, actualCmdLine);
+ actualCmdLine.addArg(filePath);
+
+ const auto& args = input.testOptions->args;
+
+ for( auto arg : input.testOptions->args )
{
- result = TestResult::Fail;
+ actualCmdLine.addArg(arg);
+ }
+
+ ExecuteResult actualExeRes;
+ TEST_RETURN_ON_DONE(spawnAndWait(context, input.outputStem, input.spawnType, actualCmdLine, actualExeRes));
+
+ // Early out if we're just collecting requirements
+ if (context->isCollectingRequirements())
+ {
+ return TestResult::Pass;
}
// Always fail if the compilation produced a failure, just
@@ -2566,18 +2625,74 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
//
if(actualExeRes.resultCode != 0 )
{
- result = TestResult::Fail;
+ return TestResult::Fail;
+ }
+
+ actualOutput = getOutput(actualExeRes);
+ return TestResult::Pass;
+}
+
+TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
+{
+ // Need to execute the stand-alone Slang compiler on the file
+ // then on the same file + `.glsl` and compare output
+ //
+ // Or, in the case of a filecheck test, instead of comparing against the
+ // +".glsl" version, we run some filecheck rules on it
+
+ String fileCheckPrefix;
+ const bool isFileCheckTest = input.testOptions->getFileCheckPrefix(fileCheckPrefix);
+
+ String actualOutput;
+ if(TestResult r = generateActualOutput(context, input, actualOutput); r != TestResult::Pass)
+ {
+ return r;
+ }
+
+ // Only generate the expected output if this is a comparison against some
+ // known-good glsl/hlsl input
+ String expectedOutput;
+ if(!isFileCheckTest)
+ {
+ if(TestResult r = generateExpectedOutput(context, input, expectedOutput); r != TestResult::Pass)
+ {
+ return r;
+ }
+ }
+
+ // Early out if we're just collecting requirements
+ if (context->isCollectingRequirements())
+ {
+ return TestResult::Pass;
+ }
+
+ TestResult result = TestResult::Pass;
+
+ if(isFileCheckTest)
+ {
+ result = _fileCheckTest(*context, input.filePath, fileCheckPrefix, actualOutput);
+ // TODO: It might be a good idea to sanity check any expected output
+ // source files against the filecheck rules if they're applicable.
+ //
+ // Something like:
+ // fileCheckTest(context, prefix="HLSL", input, filePath + ".hlsl");
+ }
+ else
+ {
+ if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
+ {
+ result = TestResult::Fail;
+ context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
+ }
}
// 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
+ // so that we can easily inspect it from the command line and
// diagnose the problem.
if (result == TestResult::Fail)
{
- String actualOutputPath = outputStem + ".actual";
+ String actualOutputPath = input.outputStem + ".actual";
Slang::File::writeAllText(actualOutputPath, actualOutput);
-
- context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
}
return result;
@@ -2695,46 +2810,10 @@ static TestResult _runHLSLComparisonTest(
String actualOutput = actualOutputBuilder.ProduceString();
- String expectedOutput;
- Slang::File::readAllText(expectedOutputPath, expectedOutput);
-
- TestResult result = TestResult::Pass;
-
- // If no expected output file was found, then we
- // expect everything to be empty
- if (expectedOutput.getLength() == 0)
- {
- if (resultCode != 0) result = TestResult::Fail;
- if (standardError.getLength() != 0) result = TestResult::Fail;
- if (standardOutput.getLength() != 0) result = TestResult::Fail;
- }
- // Otherwise we compare to the expected output
- else if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
- {
- result = TestResult::Fail;
- }
-
// Always fail if the compilation produced a failure, just
// to catch situations where, e.g., command-line options parsing
// caused the same error in both the Slang and fxc cases.
- //
- if( resultCode != 0 )
- {
- result = TestResult::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 == TestResult::Fail)
- {
- String actualOutputPath = outputStem + ".actual";
- Slang::File::writeAllText(actualOutputPath, actualOutput);
-
- context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
- }
-
- return result;
+ return _validateOutput(context, input, actualOutput, resultCode != 0);
}
static TestResult runDXBCComparisonTest(
@@ -3077,53 +3156,39 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
return TestResult::Pass;
}
- const String referenceOutputFile = findExpectedPath(input, ".expected.txt");
- if (referenceOutputFile.getLength() <= 0)
- {
- return TestResult::Fail;
- }
-
+ // Check the stdout/stderr from the compiler process
auto actualOutput = getOutput(exeRes);
- auto expectedOutput = getExpectedOutput(outputStem);
+ auto compileResult = _validateOutput(
+ context,
+ input,
+ actualOutput,
+ false,
+ "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n");
- if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
- {
- context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
-
- String actualOutputPath = outputStem + ".actual";
- Slang::File::writeAllText(actualOutputPath, actualOutput);
-
- return TestResult::Fail;
- }
-
// check against reference output
- if (!File::exists(actualOutputFile))
- {
- printf("render-test not producing expected outputs.\n");
- printf("render-test output:\n%s\n", actualOutput.getBuffer());
- return TestResult::Fail;
- }
- if (!File::exists(referenceOutputFile))
- {
- printf("referenceOutput %s not found.\n", referenceOutputFile.getBuffer());
- return TestResult::Fail;
- }
- String actualOutputContent, referenceOutputContent;
-
- File::readAllText(actualOutputFile, actualOutputContent);
- File::readAllText(referenceOutputFile, referenceOutputContent);
-
- if (SLANG_FAILED(_compareWithType(actualOutputContent.getUnownedSlice(), referenceOutputContent.getUnownedSlice())))
+ String actualOutputContent;
+ if (SLANG_FAILED(File::readAllText(actualOutputFile, actualOutputContent)))
{
context->getTestReporter()->messageFormat(
- TestMessageType::TestFailure,
- "output mismatch! actual output: {\n%s\n}, \n%s\n",
- actualOutputContent.getBuffer(),
- referenceOutputContent.getBuffer());
- return TestResult::Fail;
+ TestMessageType::RunError,
+ "Unable to read render-test output: %s\n",
+ actualOutput.getBuffer());
+ return TestResult::Fail;
}
- return TestResult::Pass;
+ String fileCheckPrefix;
+ auto bufferResult = input.testOptions->getFileCheckBufferPrefix(fileCheckPrefix)
+ ? _fileCheckTest(*context, input.filePath, fileCheckPrefix, actualOutputContent)
+ : _fileComparisonTest(
+ *context,
+ input,
+ nullptr,
+ ".expected.txt",
+ actualOutputContent,
+ [](const auto& a, const auto& e){
+ return SLANG_SUCCEEDED(_compareWithType(a.getUnownedSlice(), e.getUnownedSlice()));
+ });
+ return std::max(compileResult, bufferResult);
}
TestResult runSlangComputeComparisonTest(TestContext* context, TestInput& input)
@@ -3368,6 +3433,15 @@ TestResult runHLSLRenderComparisonTestImpl(
char const* expectedArg,
char const* actualArg)
{
+ String _fileCheckPrefix;
+ if(input.testOptions->getFileCheckPrefix(_fileCheckPrefix))
+ {
+ context->getTestReporter()->message(
+ TestMessageType::RunError,
+ "FileCheck testing isn't supported for HLSL render tests");
+ return TestResult::Fail;
+ }
+
auto filePath = input.filePath;
auto outputStem = input.outputStem;
diff --git a/tools/slang-test/test-context.cpp b/tools/slang-test/test-context.cpp
index 8d8c20adf..6c9c7f549 100644
--- a/tools/slang-test/test-context.cpp
+++ b/tools/slang-test/test-context.cpp
@@ -59,6 +59,26 @@ TestReporter* TestContext::getTestReporter()
return m_reporters[slangTestThreadIndex];
}
+SlangResult TestContext::locateFileCheck()
+{
+ DefaultSharedLibraryLoader* loader = DefaultSharedLibraryLoader::getSingleton();
+ ComPtr<ISlangSharedLibrary> library;
+ SLANG_RETURN_ON_FAIL(loader->loadSharedLibrary("slang-llvm", library.writeRef()));
+
+ if (!library)
+ {
+ return SLANG_FAIL;
+ }
+
+ using CreateFileCheckFunc = SlangResult (*)(const SlangUUID&, void**);
+ auto fn = reinterpret_cast<CreateFileCheckFunc>(library->findFuncByName("createLLVMFileCheck_V1"));
+ if(!fn)
+ {
+ return SLANG_FAIL;
+ }
+ return fn(SLANG_IID_PPV_ARGS(m_fileCheck.writeRef()));
+}
+
Result TestContext::init(const char* inExePath)
{
m_session = spCreateSession(nullptr);
@@ -68,6 +88,9 @@ Result TestContext::init(const char* inExePath)
}
exePath = inExePath;
SLANG_RETURN_ON_FAIL(TestToolUtil::getExeDirectoryPath(inExePath, exeDirectoryPath));
+
+ SLANG_RETURN_ON_FAIL(locateFileCheck());
+
return SLANG_OK;
}
diff --git a/tools/slang-test/test-context.h b/tools/slang-test/test-context.h
index fb391fb42..0abc6906f 100644
--- a/tools/slang-test/test-context.h
+++ b/tools/slang-test/test-context.h
@@ -17,6 +17,8 @@
#include "../../slang-com-ptr.h"
+#include "filecheck.h"
+
#include "options.h"
#include <mutex>
@@ -170,9 +172,13 @@ class TestContext
std::mutex mutex;
Slang::RefPtr<Slang::JSONRPCConnection> m_languageServerConnection;
+ Slang::IFileCheck* getFileCheck() { return m_fileCheck; };
+
protected:
SlangResult _createJSONRPCConnection(Slang::RefPtr<Slang::JSONRPCConnection>& out);
+ SlangResult locateFileCheck();
+
struct SharedLibraryTool
{
Slang::ComPtr<ISlangSharedLibrary> m_sharedLibrary;
@@ -186,6 +192,8 @@ protected:
SlangSession* m_session;
Slang::Dictionary<Slang::String, SharedLibraryTool> m_sharedLibTools;
+
+ Slang::ComPtr<Slang::IFileCheck> m_fileCheck;
};
#endif // TEST_CONTEXT_H_INCLUDED
diff --git a/tools/slang-test/test-reporter.h b/tools/slang-test/test-reporter.h
index a90cd6653..b92865f8a 100644
--- a/tools/slang-test/test-reporter.h
+++ b/tools/slang-test/test-reporter.h
@@ -84,6 +84,7 @@ class TestReporter : public ITestReporter
// Called for an error in the test-runner (not for an error involving a test itself).
void message(TestMessageType type, const Slang::String& errorText);
+ SLANG_ATTR_PRINTF(3, 4)
void messageFormat(TestMessageType type, char const* message, ...);
virtual SLANG_NO_THROW void SLANG_MCALL message(TestMessageType type, char const* message) override;