blob: 8dffb8050f9751c10a3dd9276bc02835c12e76b1 [file] [log] [blame]
Lei Zhang414eb602016-03-04 16:22:34 -05001//
2// Copyright (C) 2016 Google, Inc.
3//
4// All rights reserved.
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions
8// are met:
9//
10// Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12//
13// Redistributions in binary form must reproduce the above
14// copyright notice, this list of conditions and the following
15// disclaimer in the documentation and/or other materials provided
16// with the distribution.
17//
18// Neither the name of Google Inc. nor the names of its
19// contributors may be used to endorse or promote products derived
20// from this software without specific prior written permission.
21//
22// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33// POSSIBILITY OF SUCH DAMAGE.
34
35#ifndef GLSLANG_GTESTS_TEST_FIXTURE_H
36#define GLSLANG_GTESTS_TEST_FIXTURE_H
37
38#include <stdint.h>
39#include <fstream>
40#include <sstream>
41#include <streambuf>
42#include <tuple>
43
44#include <gtest/gtest.h>
45
46#include "SPIRV/GlslangToSpv.h"
47#include "SPIRV/disassemble.h"
48#include "SPIRV/doc.h"
Lei Zhang8a9b1ee2016-05-19 13:31:43 -040049#include "StandAlone/ResourceLimits.h"
Lei Zhang414eb602016-03-04 16:22:34 -050050#include "glslang/Public/ShaderLang.h"
51
52#include "Initializer.h"
53#include "Settings.h"
54
55// We need CMake to provide us the absolute path to the directory containing
56// test files, so we are certain to find those files no matter where the test
57// harness binary is generated. This provides out-of-source build capability.
58#ifndef GLSLANG_TEST_DIRECTORY
59#error \
60 "GLSLANG_TEST_DIRECTORY needs to be defined for gtest to locate test files."
61#endif
62
63namespace glslangtest {
64
65// This function is used to provide custom test name suffixes based on the
66// shader source file names. Otherwise, the test name suffixes will just be
67// numbers, which are not quite obvious.
Lei Zhangd6f0ed22016-05-16 12:50:30 -040068std::string FileNameAsCustomTestSuffix(
Lei Zhang414eb602016-03-04 16:22:34 -050069 const ::testing::TestParamInfo<std::string>& info);
70
Lei Zhangd6f0ed22016-05-16 12:50:30 -040071enum class Source {
72 GLSL,
73 HLSL,
74};
75
Lei Zhang414eb602016-03-04 16:22:34 -050076// Enum for shader compilation semantics.
77enum class Semantics {
78 OpenGL,
79 Vulkan,
80};
81
82// Enum for compilation target.
83enum class Target {
84 AST,
Lei Zhangd6f0ed22016-05-16 12:50:30 -040085 Spv,
86 BothASTAndSpv,
Lei Zhang414eb602016-03-04 16:22:34 -050087};
88
Lei Zhangd6f0ed22016-05-16 12:50:30 -040089EShLanguage GetShaderStage(const std::string& stage);
Lei Zhang414eb602016-03-04 16:22:34 -050090
Lei Zhangd6f0ed22016-05-16 12:50:30 -040091EShMessages DeriveOptions(Source, Semantics, Target);
Lei Zhang414eb602016-03-04 16:22:34 -050092
93// Reads the content of the file at the given |path|. On success, returns true
94// and the contents; otherwise, returns false and an empty string.
95std::pair<bool, std::string> ReadFile(const std::string& path);
96
97// Writes the given |contents| into the file at the given |path|. Returns true
98// on successful output.
99bool WriteFile(const std::string& path, const std::string& contents);
100
101// Returns the suffix of the given |name|.
102std::string GetSuffix(const std::string& name);
103
104// Base class for glslang integration tests. It contains many handy utility-like
105// methods such as reading shader source files, compiling into AST/SPIR-V, and
106// comparing with expected outputs.
107//
108// To write value-Parameterized tests:
109// using ValueParamTest = GlslangTest<::testing::TestWithParam<std::string>>;
110// To use as normal fixture:
111// using FixtureTest = GlslangTest<::testing::Test>;
112template <typename GT>
113class GlslangTest : public GT {
114public:
115 GlslangTest()
116 : defaultVersion(100),
117 defaultProfile(ENoProfile),
118 forceVersionProfile(false),
119 isForwardCompatible(false) {}
120
121 // Tries to load the contents from the file at the given |path|. On success,
122 // writes the contents into |contents|. On failure, errors out.
123 void tryLoadFile(const std::string& path, const std::string& tag,
124 std::string* contents)
125 {
126 bool fileReadOk;
127 std::tie(fileReadOk, *contents) = ReadFile(path);
128 ASSERT_TRUE(fileReadOk) << "Cannot open " << tag << " file: " << path;
129 }
130
131 // Checks the equality of |expected| and |real|. If they are not equal,
Lei Zhangfc697cc2016-05-17 16:43:05 -0400132 // write |real| to the given file named as |fname| if update mode is on.
Lei Zhang414eb602016-03-04 16:22:34 -0500133 void checkEqAndUpdateIfRequested(const std::string& expected,
134 const std::string& real,
135 const std::string& fname)
136 {
Lei Zhangfc697cc2016-05-17 16:43:05 -0400137 // In order to output the message we want under proper circumstances,
138 // we need the following operator<< stuff.
Lei Zhang414eb602016-03-04 16:22:34 -0500139 EXPECT_EQ(expected, real)
140 << (GlobalTestSettings.updateMode
141 ? ("Mismatch found and update mode turned on - "
142 "flushing expected result output.")
143 : "");
144
145 // Update the expected output file if requested.
146 // It looks weird to duplicate the comparison between expected_output
Lei Zhangfc697cc2016-05-17 16:43:05 -0400147 // and stream.str(). However, if creating a variable for the comparison
148 // result, we cannot have pretty print of the string diff in the above.
Lei Zhang414eb602016-03-04 16:22:34 -0500149 if (GlobalTestSettings.updateMode && expected != real) {
150 EXPECT_TRUE(WriteFile(fname, real)) << "Flushing failed";
151 }
152 }
153
Lei Zhang2f1ee452016-05-17 23:03:28 -0400154 struct ShaderResult {
155 std::string shaderName;
156 std::string output;
157 std::string error;
158 };
159
Lei Zhang414eb602016-03-04 16:22:34 -0500160 // A struct for holding all the information returned by glslang compilation
161 // and linking.
162 struct GlslangResult {
Lei Zhang2f1ee452016-05-17 23:03:28 -0400163 std::vector<ShaderResult> shaderResults;
164 std::string linkingOutput;
165 std::string linkingError;
166 std::string spirvWarningsErrors;
167 std::string spirv; // Optional SPIR-V disassembly text.
Lei Zhang414eb602016-03-04 16:22:34 -0500168 };
169
Lei Zhang2f1ee452016-05-17 23:03:28 -0400170 // Compiles and the given source |code| of the given shader |stage| into
171 // the target under the semantics conveyed via |controls|. Returns true
172 // and modifies |shader| on success.
173 bool compile(glslang::TShader* shader, const std::string& code,
Lei Zhang1b141722016-05-19 13:50:49 -0400174 const std::string& entryPointName, EShMessages controls,
175 const TBuiltInResource* resources=nullptr)
Lei Zhang414eb602016-03-04 16:22:34 -0500176 {
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400177 const char* shaderStrings = code.data();
178 const int shaderLengths = static_cast<int>(code.size());
Lei Zhang414eb602016-03-04 16:22:34 -0500179
Lei Zhang2f1ee452016-05-17 23:03:28 -0400180 shader->setStringsWithLengths(&shaderStrings, &shaderLengths, 1);
181 if (!entryPointName.empty()) shader->setEntryPoint(entryPointName.c_str());
Lei Zhang414eb602016-03-04 16:22:34 -0500182 // Reinitialize glslang if the semantics change.
John Kessenich7f349c72016-07-08 22:09:10 -0600183 GlobalTestSettings.initializer->acquire(controls);
Lei Zhang1b141722016-05-19 13:50:49 -0400184 return shader->parse(
185 (resources ? resources : &glslang::DefaultTBuiltInResource),
186 defaultVersion, isForwardCompatible, controls);
Lei Zhang2f1ee452016-05-17 23:03:28 -0400187 }
188
189 // Compiles and links the given source |code| of the given shader
190 // |stage| into the target under the semantics specified via |controls|.
191 // Returns a GlslangResult instance containing all the information generated
192 // during the process. If the target includes SPIR-V, also disassembles
193 // the result and returns disassembly text.
194 GlslangResult compileAndLink(
195 const std::string shaderName, const std::string& code,
196 const std::string& entryPointName, EShMessages controls)
197 {
198 const EShLanguage kind = GetShaderStage(GetSuffix(shaderName));
199
200 glslang::TShader shader(kind);
201 bool success = compile(&shader, code, entryPointName, controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500202
203 glslang::TProgram program;
204 program.addShader(&shader);
Lei Zhang2f1ee452016-05-17 23:03:28 -0400205 success &= program.link(controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500206
Lei Zhang17535f72016-05-04 15:55:59 -0400207 spv::SpvBuildLogger logger;
Lei Zhang09caf122016-05-02 18:11:54 -0400208
Lei Zhang2f1ee452016-05-17 23:03:28 -0400209 if (success && (controls & EShMsgSpvRules)) {
Lei Zhang414eb602016-03-04 16:22:34 -0500210 std::vector<uint32_t> spirv_binary;
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400211 glslang::GlslangToSpv(*program.getIntermediate(kind),
Lei Zhang17535f72016-05-04 15:55:59 -0400212 spirv_binary, &logger);
Lei Zhang414eb602016-03-04 16:22:34 -0500213
214 std::ostringstream disassembly_stream;
215 spv::Parameterize();
216 spv::Disassemble(disassembly_stream, spirv_binary);
Lei Zhang2f1ee452016-05-17 23:03:28 -0400217 return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
Lei Zhang414eb602016-03-04 16:22:34 -0500218 program.getInfoLog(), program.getInfoDebugLog(),
Lei Zhang17535f72016-05-04 15:55:59 -0400219 logger.getAllMessages(), disassembly_stream.str()};
Lei Zhang414eb602016-03-04 16:22:34 -0500220 } else {
Lei Zhang2f1ee452016-05-17 23:03:28 -0400221 return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
222 program.getInfoLog(), program.getInfoDebugLog(), "", ""};
223 }
224 }
225
226 void outputResultToStream(std::ostringstream* stream,
227 const GlslangResult& result,
228 EShMessages controls)
229 {
230 const auto outputIfNotEmpty = [&stream](const std::string& str) {
231 if (!str.empty()) *stream << str << "\n";
232 };
233
234 for (const auto& shaderResult : result.shaderResults) {
235 *stream << shaderResult.shaderName << "\n";
236 outputIfNotEmpty(shaderResult.output);
237 outputIfNotEmpty(shaderResult.error);
238 }
239 outputIfNotEmpty(result.linkingOutput);
240 outputIfNotEmpty(result.linkingError);
241 *stream << result.spirvWarningsErrors;
242
243 if (controls & EShMsgSpvRules) {
244 *stream
245 << (result.spirv.empty()
246 ? "SPIR-V is not generated for failed compile or link\n"
247 : result.spirv);
Lei Zhang414eb602016-03-04 16:22:34 -0500248 }
249 }
250
251 void loadFileCompileAndCheck(const std::string& testDir,
252 const std::string& testName,
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400253 Source source,
254 Semantics semantics,
255 Target target,
256 const std::string& entryPointName="")
Lei Zhang414eb602016-03-04 16:22:34 -0500257 {
258 const std::string inputFname = testDir + "/" + testName;
259 const std::string expectedOutputFname =
260 testDir + "/baseResults/" + testName + ".out";
261 std::string input, expectedOutput;
262
263 tryLoadFile(inputFname, "input", &input);
264 tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
265
Lei Zhang2f1ee452016-05-17 23:03:28 -0400266 const EShMessages controls = DeriveOptions(source, semantics, target);
John Kessenicha86836e2016-07-09 14:50:57 -0600267 GlslangResult result = compileAndLink(testName, input, entryPointName, controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500268
269 // Generate the hybrid output in the way of glslangValidator.
270 std::ostringstream stream;
Lei Zhang2f1ee452016-05-17 23:03:28 -0400271 outputResultToStream(&stream, result, controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500272
273 checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
274 expectedOutputFname);
275 }
276
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400277 // Preprocesses the given |source| code. On success, returns true, the
Lei Zhang414eb602016-03-04 16:22:34 -0500278 // preprocessed shader, and warning messages. Otherwise, returns false, an
279 // empty string, and error messages.
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400280 std::tuple<bool, std::string, std::string> preprocess(
Lei Zhang414eb602016-03-04 16:22:34 -0500281 const std::string& source)
282 {
283 const char* shaderStrings = source.data();
284 const int shaderLengths = static_cast<int>(source.size());
285
286 glslang::TShader shader(EShLangVertex);
287 shader.setStringsWithLengths(&shaderStrings, &shaderLengths, 1);
288 std::string ppShader;
289 glslang::TShader::ForbidInclude includer;
290 const bool success = shader.preprocess(
291 &glslang::DefaultTBuiltInResource, defaultVersion, defaultProfile,
John Kessenicha86836e2016-07-09 14:50:57 -0600292 forceVersionProfile, isForwardCompatible, (EShMessages)(EShMsgOnlyPreprocessor | EShMsgCascadingErrors),
Lei Zhang414eb602016-03-04 16:22:34 -0500293 &ppShader, includer);
294
295 std::string log = shader.getInfoLog();
296 log += shader.getInfoDebugLog();
297 if (success) {
298 return std::make_tuple(true, ppShader, log);
299 } else {
300 return std::make_tuple(false, "", log);
301 }
302 }
303
304 void loadFilePreprocessAndCheck(const std::string& testDir,
305 const std::string& testName)
306 {
307 const std::string inputFname = testDir + "/" + testName;
308 const std::string expectedOutputFname =
309 testDir + "/baseResults/" + testName + ".out";
310 const std::string expectedErrorFname =
311 testDir + "/baseResults/" + testName + ".err";
312 std::string input, expectedOutput, expectedError;
313
314 tryLoadFile(inputFname, "input", &input);
315 tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
316 tryLoadFile(expectedErrorFname, "expected error", &expectedError);
317
318 bool ppOk;
319 std::string output, error;
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400320 std::tie(ppOk, output, error) = preprocess(input);
Lei Zhang414eb602016-03-04 16:22:34 -0500321 if (!output.empty()) output += '\n';
322 if (!error.empty()) error += '\n';
323
324 checkEqAndUpdateIfRequested(expectedOutput, output,
325 expectedOutputFname);
326 checkEqAndUpdateIfRequested(expectedError, error,
327 expectedErrorFname);
328 }
329
330private:
331 const int defaultVersion;
332 const EProfile defaultProfile;
333 const bool forceVersionProfile;
334 const bool isForwardCompatible;
335};
336
337} // namespace glslangtest
338
339#endif // GLSLANG_GTESTS_TEST_FIXTURE_H