1 // Copyright (c) 2015-2016 The Khronos Group Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef TEST_TEST_FIXTURE_H_
16 #define TEST_TEST_FIXTURE_H_
17 
18 #include <algorithm>
19 #include <string>
20 #include <vector>
21 
22 #include "test/unit_spirv.h"
23 
24 namespace spvtest {
25 
26 // RAII for spv_context.
27 struct ScopedContext {
28   ScopedContext(spv_target_env env = SPV_ENV_UNIVERSAL_1_0)
contextScopedContext29       : context(spvContextCreate(env)) {}
~ScopedContextScopedContext30   ~ScopedContext() { spvContextDestroy(context); }
31   spv_context context;
32 };
33 
34 // Common setup for TextToBinary tests. SetText() should be called to populate
35 // the actual test text.
36 template <typename T>
37 class TextToBinaryTestBase : public T {
38  public:
39   // Shorthand for SPIR-V compilation result.
40   using SpirvVector = std::vector<uint32_t>;
41 
42   // Offset into a SpirvVector at which the first instruction starts.
43   static const SpirvVector::size_type kFirstInstruction = 5;
44 
TextToBinaryTestBase()45   TextToBinaryTestBase() : diagnostic(nullptr), text(), binary(nullptr) {
46     char textStr[] = "substitute the text member variable with your test";
47     text = {textStr, strlen(textStr)};
48   }
49 
~TextToBinaryTestBase()50   ~TextToBinaryTestBase() override {
51     DestroyBinary();
52     if (diagnostic) spvDiagnosticDestroy(diagnostic);
53   }
54 
55   // Returns subvector v[from:end).
Subvector(const SpirvVector & v,SpirvVector::size_type from)56   SpirvVector Subvector(const SpirvVector& v, SpirvVector::size_type from) {
57     assert(from <= v.size());
58     return SpirvVector(v.begin() + from, v.end());
59   }
60 
61   // Compiles SPIR-V text in the given assembly syntax format, asserting
62   // compilation success. Returns the compiled code.
63   SpirvVector CompileSuccessfully(const std::string& txt,
64                                   spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
65     DestroyBinary();
66     DestroyDiagnostic();
67     spv_result_t status =
68         spvTextToBinary(ScopedContext(env).context, txt.c_str(), txt.size(),
69                         &binary, &diagnostic);
70     EXPECT_EQ(SPV_SUCCESS, status) << txt;
71     SpirvVector code_copy;
72     if (status == SPV_SUCCESS) {
73       code_copy = SpirvVector(binary->code, binary->code + binary->wordCount);
74       DestroyBinary();
75     } else {
76       spvDiagnosticPrint(diagnostic);
77     }
78     return code_copy;
79   }
80 
81   // Compiles SPIR-V text with the given format, asserting compilation failure.
82   // Returns the error message(s).
83   std::string CompileFailure(const std::string& txt,
84                              spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
85     DestroyBinary();
86     DestroyDiagnostic();
87     EXPECT_NE(SPV_SUCCESS,
88               spvTextToBinary(ScopedContext(env).context, txt.c_str(),
89                               txt.size(), &binary, &diagnostic))
90         << txt;
91     DestroyBinary();
92     return diagnostic->error;
93   }
94 
95   // Potentially flip the words in the binary representation to the other
96   // endianness
97   template <class It>
MaybeFlipWords(bool flip_words,It begin,It end)98   void MaybeFlipWords(bool flip_words, It begin, It end) {
99     SCOPED_TRACE(flip_words ? "Flipped Endianness" : "Normal Endianness");
100     if (flip_words) {
101       std::transform(begin, end, begin, [](const uint32_t raw_word) {
102         return spvFixWord(raw_word, I32_ENDIAN_HOST == I32_ENDIAN_BIG
103                                         ? SPV_ENDIANNESS_LITTLE
104                                         : SPV_ENDIANNESS_BIG);
105       });
106     }
107   }
108 
109   // Encodes SPIR-V text into binary and then decodes the binary using
110   // given options. Returns the decoded text.
111   std::string EncodeAndDecodeSuccessfully(
112       const std::string& txt,
113       uint32_t disassemble_options = SPV_BINARY_TO_TEXT_OPTION_NONE,
114       spv_target_env env = SPV_ENV_UNIVERSAL_1_0, bool flip_words = false) {
115     DestroyBinary();
116     DestroyDiagnostic();
117     ScopedContext context(env);
118     disassemble_options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER;
119     spv_result_t error = spvTextToBinary(context.context, txt.c_str(),
120                                          txt.size(), &binary, &diagnostic);
121     if (error) {
122       spvDiagnosticPrint(diagnostic);
123       spvDiagnosticDestroy(diagnostic);
124     }
125     EXPECT_EQ(SPV_SUCCESS, error);
126     if (!binary) return "";
127 
128     MaybeFlipWords(flip_words, binary->code, binary->code + binary->wordCount);
129 
130     spv_text decoded_text;
131     error = spvBinaryToText(context.context, binary->code, binary->wordCount,
132                             disassemble_options, &decoded_text, &diagnostic);
133     if (error) {
134       spvDiagnosticPrint(diagnostic);
135       spvDiagnosticDestroy(diagnostic);
136     }
137     EXPECT_EQ(SPV_SUCCESS, error) << txt;
138 
139     const std::string decoded_string = decoded_text->str;
140     spvTextDestroy(decoded_text);
141 
142     return decoded_string;
143   }
144 
145   // Encodes SPIR-V text into binary. This is expected to succeed.
146   // The given words are then appended to the binary, and the result
147   // is then decoded. This is expected to fail.
148   // Returns the error message.
EncodeSuccessfullyDecodeFailed(const std::string & txt,const SpirvVector & words_to_append)149   std::string EncodeSuccessfullyDecodeFailed(
150       const std::string& txt, const SpirvVector& words_to_append) {
151     DestroyBinary();
152     DestroyDiagnostic();
153     SpirvVector code =
154         spvtest::Concatenate({CompileSuccessfully(txt), words_to_append});
155 
156     spv_text decoded_text;
157     EXPECT_NE(SPV_SUCCESS,
158               spvBinaryToText(ScopedContext().context, code.data(), code.size(),
159                               SPV_BINARY_TO_TEXT_OPTION_NONE, &decoded_text,
160                               &diagnostic));
161     if (diagnostic) {
162       std::string error_message = diagnostic->error;
163       spvDiagnosticDestroy(diagnostic);
164       diagnostic = nullptr;
165       return error_message;
166     }
167     return "";
168   }
169 
170   // Compiles SPIR-V text, asserts success, and returns the words representing
171   // the instructions.  In particular, skip the words in the SPIR-V header.
172   SpirvVector CompiledInstructions(const std::string& txt,
173                                    spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
174     const SpirvVector code = CompileSuccessfully(txt, env);
175     SpirvVector result;
176     // Extract just the instructions.
177     // If the code fails to compile, then return the empty vector.
178     // In any case, don't crash or invoke undefined behaviour.
179     if (code.size() >= kFirstInstruction)
180       result = Subvector(code, kFirstInstruction);
181     return result;
182   }
183 
SetText(const std::string & code)184   void SetText(const std::string& code) {
185     textString = code;
186     text.str = textString.c_str();
187     text.length = textString.size();
188   }
189 
190   // Destroys the binary, if it exists.
DestroyBinary()191   void DestroyBinary() {
192     spvBinaryDestroy(binary);
193     binary = nullptr;
194   }
195 
196   // Destroys the diagnostic, if it exists.
DestroyDiagnostic()197   void DestroyDiagnostic() {
198     spvDiagnosticDestroy(diagnostic);
199     diagnostic = nullptr;
200   }
201 
202   spv_diagnostic diagnostic;
203 
204   std::string textString;
205   spv_text_t text;
206   spv_binary binary;
207 };
208 
209 using TextToBinaryTest = TextToBinaryTestBase<::testing::Test>;
210 }  // namespace spvtest
211 
212 using RoundTripTest =
213     spvtest::TextToBinaryTestBase<::testing::TestWithParam<std::string>>;
214 
215 #endif  // TEST_TEST_FIXTURE_H_
216