blob: 81930125b201ea48b961af70d2d60d1d26d80d8c [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
John Kessenich66ec80e2016-08-05 14:04:23 -060038#include <cstdint>
Lei Zhang414eb602016-03-04 16:22:34 -050039#include <fstream>
40#include <sstream>
41#include <streambuf>
42#include <tuple>
steve-lunarga8456412016-08-17 16:18:06 -060043#include <string>
Lei Zhang414eb602016-03-04 16:22:34 -050044
45#include <gtest/gtest.h>
46
47#include "SPIRV/GlslangToSpv.h"
48#include "SPIRV/disassemble.h"
49#include "SPIRV/doc.h"
steve-lunarga8456412016-08-17 16:18:06 -060050#include "SPIRV/SPVRemapper.h"
Lei Zhang8a9b1ee2016-05-19 13:31:43 -040051#include "StandAlone/ResourceLimits.h"
Lei Zhang414eb602016-03-04 16:22:34 -050052#include "glslang/Public/ShaderLang.h"
53
54#include "Initializer.h"
55#include "Settings.h"
56
57// We need CMake to provide us the absolute path to the directory containing
58// test files, so we are certain to find those files no matter where the test
59// harness binary is generated. This provides out-of-source build capability.
60#ifndef GLSLANG_TEST_DIRECTORY
61#error \
62 "GLSLANG_TEST_DIRECTORY needs to be defined for gtest to locate test files."
63#endif
64
65namespace glslangtest {
66
67// This function is used to provide custom test name suffixes based on the
68// shader source file names. Otherwise, the test name suffixes will just be
69// numbers, which are not quite obvious.
Lei Zhangd6f0ed22016-05-16 12:50:30 -040070std::string FileNameAsCustomTestSuffix(
Lei Zhang414eb602016-03-04 16:22:34 -050071 const ::testing::TestParamInfo<std::string>& info);
72
Lei Zhangd6f0ed22016-05-16 12:50:30 -040073enum class Source {
74 GLSL,
75 HLSL,
76};
77
Lei Zhang414eb602016-03-04 16:22:34 -050078// Enum for shader compilation semantics.
79enum class Semantics {
80 OpenGL,
81 Vulkan,
82};
83
84// Enum for compilation target.
85enum class Target {
86 AST,
Lei Zhangd6f0ed22016-05-16 12:50:30 -040087 Spv,
88 BothASTAndSpv,
Lei Zhang414eb602016-03-04 16:22:34 -050089};
90
Lei Zhangd6f0ed22016-05-16 12:50:30 -040091EShLanguage GetShaderStage(const std::string& stage);
Lei Zhang414eb602016-03-04 16:22:34 -050092
Lei Zhangd6f0ed22016-05-16 12:50:30 -040093EShMessages DeriveOptions(Source, Semantics, Target);
Lei Zhang414eb602016-03-04 16:22:34 -050094
95// Reads the content of the file at the given |path|. On success, returns true
96// and the contents; otherwise, returns false and an empty string.
97std::pair<bool, std::string> ReadFile(const std::string& path);
steve-lunarga8456412016-08-17 16:18:06 -060098std::pair<bool, std::vector<std::uint32_t> > ReadSpvBinaryFile(const std::string& path);
Lei Zhang414eb602016-03-04 16:22:34 -050099
100// Writes the given |contents| into the file at the given |path|. Returns true
101// on successful output.
102bool WriteFile(const std::string& path, const std::string& contents);
103
104// Returns the suffix of the given |name|.
105std::string GetSuffix(const std::string& name);
106
107// Base class for glslang integration tests. It contains many handy utility-like
108// methods such as reading shader source files, compiling into AST/SPIR-V, and
109// comparing with expected outputs.
110//
111// To write value-Parameterized tests:
112// using ValueParamTest = GlslangTest<::testing::TestWithParam<std::string>>;
113// To use as normal fixture:
114// using FixtureTest = GlslangTest<::testing::Test>;
115template <typename GT>
116class GlslangTest : public GT {
117public:
118 GlslangTest()
119 : defaultVersion(100),
120 defaultProfile(ENoProfile),
121 forceVersionProfile(false),
122 isForwardCompatible(false) {}
123
124 // Tries to load the contents from the file at the given |path|. On success,
125 // writes the contents into |contents|. On failure, errors out.
126 void tryLoadFile(const std::string& path, const std::string& tag,
127 std::string* contents)
128 {
129 bool fileReadOk;
130 std::tie(fileReadOk, *contents) = ReadFile(path);
131 ASSERT_TRUE(fileReadOk) << "Cannot open " << tag << " file: " << path;
132 }
133
steve-lunarga8456412016-08-17 16:18:06 -0600134 // Tries to load the contents from the file at the given |path|. On success,
135 // writes the contents into |contents|. On failure, errors out.
136 void tryLoadSpvFile(const std::string& path, const std::string& tag,
137 std::vector<uint32_t>& contents)
138 {
139 bool fileReadOk;
140 std::tie(fileReadOk, contents) = ReadSpvBinaryFile(path);
141 ASSERT_TRUE(fileReadOk) << "Cannot open " << tag << " file: " << path;
142 }
143
Lei Zhang414eb602016-03-04 16:22:34 -0500144 // Checks the equality of |expected| and |real|. If they are not equal,
Lei Zhangfc697cc2016-05-17 16:43:05 -0400145 // write |real| to the given file named as |fname| if update mode is on.
Lei Zhang414eb602016-03-04 16:22:34 -0500146 void checkEqAndUpdateIfRequested(const std::string& expected,
147 const std::string& real,
148 const std::string& fname)
149 {
Lei Zhangfc697cc2016-05-17 16:43:05 -0400150 // In order to output the message we want under proper circumstances,
151 // we need the following operator<< stuff.
Lei Zhang414eb602016-03-04 16:22:34 -0500152 EXPECT_EQ(expected, real)
153 << (GlobalTestSettings.updateMode
154 ? ("Mismatch found and update mode turned on - "
155 "flushing expected result output.")
156 : "");
157
158 // Update the expected output file if requested.
159 // It looks weird to duplicate the comparison between expected_output
Lei Zhangfc697cc2016-05-17 16:43:05 -0400160 // and stream.str(). However, if creating a variable for the comparison
161 // result, we cannot have pretty print of the string diff in the above.
Lei Zhang414eb602016-03-04 16:22:34 -0500162 if (GlobalTestSettings.updateMode && expected != real) {
163 EXPECT_TRUE(WriteFile(fname, real)) << "Flushing failed";
164 }
165 }
166
Lei Zhang2f1ee452016-05-17 23:03:28 -0400167 struct ShaderResult {
168 std::string shaderName;
169 std::string output;
170 std::string error;
171 };
172
Lei Zhang414eb602016-03-04 16:22:34 -0500173 // A struct for holding all the information returned by glslang compilation
174 // and linking.
175 struct GlslangResult {
Lei Zhang2f1ee452016-05-17 23:03:28 -0400176 std::vector<ShaderResult> shaderResults;
177 std::string linkingOutput;
178 std::string linkingError;
179 std::string spirvWarningsErrors;
180 std::string spirv; // Optional SPIR-V disassembly text.
Lei Zhang414eb602016-03-04 16:22:34 -0500181 };
182
Lei Zhang2f1ee452016-05-17 23:03:28 -0400183 // Compiles and the given source |code| of the given shader |stage| into
184 // the target under the semantics conveyed via |controls|. Returns true
185 // and modifies |shader| on success.
186 bool compile(glslang::TShader* shader, const std::string& code,
Lei Zhang1b141722016-05-19 13:50:49 -0400187 const std::string& entryPointName, EShMessages controls,
188 const TBuiltInResource* resources=nullptr)
Lei Zhang414eb602016-03-04 16:22:34 -0500189 {
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400190 const char* shaderStrings = code.data();
191 const int shaderLengths = static_cast<int>(code.size());
Lei Zhang414eb602016-03-04 16:22:34 -0500192
Lei Zhang2f1ee452016-05-17 23:03:28 -0400193 shader->setStringsWithLengths(&shaderStrings, &shaderLengths, 1);
194 if (!entryPointName.empty()) shader->setEntryPoint(entryPointName.c_str());
Lei Zhang1b141722016-05-19 13:50:49 -0400195 return shader->parse(
196 (resources ? resources : &glslang::DefaultTBuiltInResource),
197 defaultVersion, isForwardCompatible, controls);
Lei Zhang2f1ee452016-05-17 23:03:28 -0400198 }
199
200 // Compiles and links the given source |code| of the given shader
201 // |stage| into the target under the semantics specified via |controls|.
202 // Returns a GlslangResult instance containing all the information generated
203 // during the process. If the target includes SPIR-V, also disassembles
204 // the result and returns disassembly text.
205 GlslangResult compileAndLink(
206 const std::string shaderName, const std::string& code,
207 const std::string& entryPointName, EShMessages controls)
208 {
209 const EShLanguage kind = GetShaderStage(GetSuffix(shaderName));
210
211 glslang::TShader shader(kind);
212 bool success = compile(&shader, code, entryPointName, controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500213
214 glslang::TProgram program;
215 program.addShader(&shader);
Lei Zhang2f1ee452016-05-17 23:03:28 -0400216 success &= program.link(controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500217
Lei Zhang17535f72016-05-04 15:55:59 -0400218 spv::SpvBuildLogger logger;
Lei Zhang09caf122016-05-02 18:11:54 -0400219
Lei Zhang2f1ee452016-05-17 23:03:28 -0400220 if (success && (controls & EShMsgSpvRules)) {
Lei Zhang414eb602016-03-04 16:22:34 -0500221 std::vector<uint32_t> spirv_binary;
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400222 glslang::GlslangToSpv(*program.getIntermediate(kind),
Lei Zhang17535f72016-05-04 15:55:59 -0400223 spirv_binary, &logger);
Lei Zhang414eb602016-03-04 16:22:34 -0500224
225 std::ostringstream disassembly_stream;
226 spv::Parameterize();
227 spv::Disassemble(disassembly_stream, spirv_binary);
Lei Zhang2f1ee452016-05-17 23:03:28 -0400228 return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
Lei Zhang414eb602016-03-04 16:22:34 -0500229 program.getInfoLog(), program.getInfoDebugLog(),
Lei Zhang17535f72016-05-04 15:55:59 -0400230 logger.getAllMessages(), disassembly_stream.str()};
Lei Zhang414eb602016-03-04 16:22:34 -0500231 } else {
Lei Zhang2f1ee452016-05-17 23:03:28 -0400232 return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
233 program.getInfoLog(), program.getInfoDebugLog(), "", ""};
234 }
235 }
236
steve-lunarga8456412016-08-17 16:18:06 -0600237 // This is like compileAndLink but with remapping of the SPV binary
238 // through spirvbin_t::remap(). While technically this could be merged
239 // with compileAndLink() above (with the remap step optionally being a no-op)
240 // it is given separately here for ease of future extraction.
241 GlslangResult compileLinkRemap(
242 const std::string shaderName, const std::string& code,
243 const std::string& entryPointName, EShMessages controls,
244 const unsigned int remapOptions = spv::spirvbin_t::NONE)
245 {
246 const EShLanguage kind = GetShaderStage(GetSuffix(shaderName));
247
248 glslang::TShader shader(kind);
249 bool success = compile(&shader, code, entryPointName, controls);
250
251 glslang::TProgram program;
252 program.addShader(&shader);
253 success &= program.link(controls);
254
255 spv::SpvBuildLogger logger;
256
257 if (success && (controls & EShMsgSpvRules)) {
258 std::vector<uint32_t> spirv_binary;
259 glslang::GlslangToSpv(*program.getIntermediate(kind),
260 spirv_binary, &logger);
261
262 spv::spirvbin_t(0 /*verbosity*/).remap(spirv_binary, remapOptions);
263
264 std::ostringstream disassembly_stream;
265 spv::Parameterize();
266 spv::Disassemble(disassembly_stream, spirv_binary);
267 return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
268 program.getInfoLog(), program.getInfoDebugLog(),
269 logger.getAllMessages(), disassembly_stream.str()};
270 } else {
271 return {{{shaderName, shader.getInfoLog(), shader.getInfoDebugLog()},},
272 program.getInfoLog(), program.getInfoDebugLog(), "", ""};
273 }
274 }
275
276 // remap the binary in 'code' with the options in remapOptions
277 GlslangResult remap(
278 const std::string shaderName, const std::vector<uint32_t>& code,
279 EShMessages controls,
280 const unsigned int remapOptions = spv::spirvbin_t::NONE)
281 {
282 if ((controls & EShMsgSpvRules)) {
283 std::vector<uint32_t> spirv_binary(code); // scratch copy
284
285 spv::spirvbin_t(0 /*verbosity*/).remap(spirv_binary, remapOptions);
286
287 std::ostringstream disassembly_stream;
288 spv::Parameterize();
289 spv::Disassemble(disassembly_stream, spirv_binary);
290
291 return {{{shaderName, "", ""},},
292 "", "",
293 "", disassembly_stream.str()};
294 } else {
295 return {{{shaderName, "", ""},}, "", "", "", ""};
296 }
297 }
298
Lei Zhang2f1ee452016-05-17 23:03:28 -0400299 void outputResultToStream(std::ostringstream* stream,
300 const GlslangResult& result,
301 EShMessages controls)
302 {
303 const auto outputIfNotEmpty = [&stream](const std::string& str) {
304 if (!str.empty()) *stream << str << "\n";
305 };
306
307 for (const auto& shaderResult : result.shaderResults) {
308 *stream << shaderResult.shaderName << "\n";
309 outputIfNotEmpty(shaderResult.output);
310 outputIfNotEmpty(shaderResult.error);
311 }
312 outputIfNotEmpty(result.linkingOutput);
313 outputIfNotEmpty(result.linkingError);
314 *stream << result.spirvWarningsErrors;
315
316 if (controls & EShMsgSpvRules) {
317 *stream
318 << (result.spirv.empty()
319 ? "SPIR-V is not generated for failed compile or link\n"
320 : result.spirv);
Lei Zhang414eb602016-03-04 16:22:34 -0500321 }
322 }
323
324 void loadFileCompileAndCheck(const std::string& testDir,
325 const std::string& testName,
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400326 Source source,
327 Semantics semantics,
328 Target target,
329 const std::string& entryPointName="")
Lei Zhang414eb602016-03-04 16:22:34 -0500330 {
331 const std::string inputFname = testDir + "/" + testName;
332 const std::string expectedOutputFname =
333 testDir + "/baseResults/" + testName + ".out";
334 std::string input, expectedOutput;
335
336 tryLoadFile(inputFname, "input", &input);
337 tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
338
Lei Zhang2f1ee452016-05-17 23:03:28 -0400339 const EShMessages controls = DeriveOptions(source, semantics, target);
John Kessenicha86836e2016-07-09 14:50:57 -0600340 GlslangResult result = compileAndLink(testName, input, entryPointName, controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500341
342 // Generate the hybrid output in the way of glslangValidator.
343 std::ostringstream stream;
Lei Zhang2f1ee452016-05-17 23:03:28 -0400344 outputResultToStream(&stream, result, controls);
Lei Zhang414eb602016-03-04 16:22:34 -0500345
346 checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
347 expectedOutputFname);
348 }
349
steve-lunarga8456412016-08-17 16:18:06 -0600350 void loadFileCompileRemapAndCheck(const std::string& testDir,
351 const std::string& testName,
352 Source source,
353 Semantics semantics,
354 Target target,
355 const std::string& entryPointName="",
356 const unsigned int remapOptions = spv::spirvbin_t::NONE)
357 {
358 const std::string inputFname = testDir + "/" + testName;
359 const std::string expectedOutputFname =
360 testDir + "/baseResults/" + testName + ".out";
361 std::string input, expectedOutput;
362
363 tryLoadFile(inputFname, "input", &input);
364 tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
365
366 const EShMessages controls = DeriveOptions(source, semantics, target);
367 GlslangResult result = compileLinkRemap(testName, input, entryPointName, controls, remapOptions);
368
369 // Generate the hybrid output in the way of glslangValidator.
370 std::ostringstream stream;
371 outputResultToStream(&stream, result, controls);
372
373 checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
374 expectedOutputFname);
375 }
376
377 void loadFileRemapAndCheck(const std::string& testDir,
378 const std::string& testName,
379 Source source,
380 Semantics semantics,
381 Target target,
382 const unsigned int remapOptions = spv::spirvbin_t::NONE)
383 {
384 const std::string inputFname = testDir + "/" + testName;
385 const std::string expectedOutputFname =
386 testDir + "/baseResults/" + testName + ".out";
387 std::vector<std::uint32_t> input;
388 std::string expectedOutput;
389
390 tryLoadSpvFile(inputFname, "input", input);
391 tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
392
393 const EShMessages controls = DeriveOptions(source, semantics, target);
394 GlslangResult result = remap(testName, input, controls, remapOptions);
395
396 // Generate the hybrid output in the way of glslangValidator.
397 std::ostringstream stream;
398 outputResultToStream(&stream, result, controls);
399
400 checkEqAndUpdateIfRequested(expectedOutput, stream.str(),
401 expectedOutputFname);
402 }
403
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400404 // Preprocesses the given |source| code. On success, returns true, the
Lei Zhang414eb602016-03-04 16:22:34 -0500405 // preprocessed shader, and warning messages. Otherwise, returns false, an
406 // empty string, and error messages.
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400407 std::tuple<bool, std::string, std::string> preprocess(
Lei Zhang414eb602016-03-04 16:22:34 -0500408 const std::string& source)
409 {
410 const char* shaderStrings = source.data();
411 const int shaderLengths = static_cast<int>(source.size());
412
413 glslang::TShader shader(EShLangVertex);
414 shader.setStringsWithLengths(&shaderStrings, &shaderLengths, 1);
415 std::string ppShader;
416 glslang::TShader::ForbidInclude includer;
417 const bool success = shader.preprocess(
418 &glslang::DefaultTBuiltInResource, defaultVersion, defaultProfile,
John Kessenicha86836e2016-07-09 14:50:57 -0600419 forceVersionProfile, isForwardCompatible, (EShMessages)(EShMsgOnlyPreprocessor | EShMsgCascadingErrors),
Lei Zhang414eb602016-03-04 16:22:34 -0500420 &ppShader, includer);
421
422 std::string log = shader.getInfoLog();
423 log += shader.getInfoDebugLog();
424 if (success) {
425 return std::make_tuple(true, ppShader, log);
426 } else {
427 return std::make_tuple(false, "", log);
428 }
429 }
430
431 void loadFilePreprocessAndCheck(const std::string& testDir,
432 const std::string& testName)
433 {
434 const std::string inputFname = testDir + "/" + testName;
435 const std::string expectedOutputFname =
436 testDir + "/baseResults/" + testName + ".out";
437 const std::string expectedErrorFname =
438 testDir + "/baseResults/" + testName + ".err";
439 std::string input, expectedOutput, expectedError;
440
441 tryLoadFile(inputFname, "input", &input);
442 tryLoadFile(expectedOutputFname, "expected output", &expectedOutput);
443 tryLoadFile(expectedErrorFname, "expected error", &expectedError);
444
445 bool ppOk;
446 std::string output, error;
Lei Zhangd6f0ed22016-05-16 12:50:30 -0400447 std::tie(ppOk, output, error) = preprocess(input);
Lei Zhang414eb602016-03-04 16:22:34 -0500448 if (!output.empty()) output += '\n';
449 if (!error.empty()) error += '\n';
450
451 checkEqAndUpdateIfRequested(expectedOutput, output,
452 expectedOutputFname);
453 checkEqAndUpdateIfRequested(expectedError, error,
454 expectedErrorFname);
455 }
456
457private:
458 const int defaultVersion;
459 const EProfile defaultProfile;
460 const bool forceVersionProfile;
461 const bool isForwardCompatible;
462};
463
464} // namespace glslangtest
465
466#endif // GLSLANG_GTESTS_TEST_FIXTURE_H