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 <string>
19 #include <vector>
20 
21 #include "test/unit_spirv.h"
22 
23 namespace spvtest {
24 
25 // RAII for spv_context.
26 struct ScopedContext {
27   ScopedContext(spv_target_env env = SPV_ENV_UNIVERSAL_1_0)
contextScopedContext28       : context(spvContextCreate(env)) {}
~ScopedContextScopedContext29   ~ScopedContext() { spvContextDestroy(context); }
30   spv_context context;
31 };
32 
33 // Common setup for TextToBinary tests. SetText() should be called to populate
34 // the actual test text.
35 template <typename T>
36 class TextToBinaryTestBase : public T {
37  public:
38   // Shorthand for SPIR-V compilation result.
39   using SpirvVector = std::vector<uint32_t>;
40 
41   // Offset into a SpirvVector at which the first instruction starts.
42   static const SpirvVector::size_type kFirstInstruction = 5;
43 
TextToBinaryTestBase()44   TextToBinaryTestBase() : diagnostic(nullptr), text(), binary(nullptr) {
45     char textStr[] = "substitute the text member variable with your test";
46     text = {textStr, strlen(textStr)};
47   }
48 
~TextToBinaryTestBase()49   virtual ~TextToBinaryTestBase() {
50     DestroyBinary();
51     if (diagnostic) spvDiagnosticDestroy(diagnostic);
52   }
53 
54   // Returns subvector v[from:end).
Subvector(const SpirvVector & v,SpirvVector::size_type from)55   SpirvVector Subvector(const SpirvVector& v, SpirvVector::size_type from) {
56     assert(from <= v.size());
57     return SpirvVector(v.begin() + from, v.end());
58   }
59 
60   // Compiles SPIR-V text in the given assembly syntax format, asserting
61   // compilation success. Returns the compiled code.
62   SpirvVector CompileSuccessfully(const std::string& txt,
63                                   spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
64     DestroyBinary();
65     DestroyDiagnostic();
66     spv_result_t status =
67         spvTextToBinary(ScopedContext(env).context, txt.c_str(), txt.size(),
68                         &binary, &diagnostic);
69     EXPECT_EQ(SPV_SUCCESS, status) << txt;
70     SpirvVector code_copy;
71     if (status == SPV_SUCCESS) {
72       code_copy = SpirvVector(binary->code, binary->code + binary->wordCount);
73       DestroyBinary();
74     } else {
75       spvDiagnosticPrint(diagnostic);
76     }
77     return code_copy;
78   }
79 
80   // Compiles SPIR-V text with the given format, asserting compilation failure.
81   // Returns the error message(s).
82   std::string CompileFailure(const std::string& txt,
83                              spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
84     DestroyBinary();
85     DestroyDiagnostic();
86     EXPECT_NE(SPV_SUCCESS,
87               spvTextToBinary(ScopedContext(env).context, txt.c_str(),
88                               txt.size(), &binary, &diagnostic))
89         << txt;
90     DestroyBinary();
91     return diagnostic->error;
92   }
93 
94   // Encodes SPIR-V text into binary and then decodes the binary using
95   // given options. Returns the decoded text.
96   std::string EncodeAndDecodeSuccessfully(
97       const std::string& txt,
98       uint32_t disassemble_options = SPV_BINARY_TO_TEXT_OPTION_NONE,
99       spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
100     DestroyBinary();
101     DestroyDiagnostic();
102     ScopedContext context(env);
103     disassemble_options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER;
104     spv_result_t error = spvTextToBinary(context.context, txt.c_str(),
105                                          txt.size(), &binary, &diagnostic);
106     if (error) {
107       spvDiagnosticPrint(diagnostic);
108       spvDiagnosticDestroy(diagnostic);
109     }
110     EXPECT_EQ(SPV_SUCCESS, error);
111     if (!binary) return "";
112 
113     spv_text decoded_text;
114     error = spvBinaryToText(context.context, binary->code, binary->wordCount,
115                             disassemble_options, &decoded_text, &diagnostic);
116     if (error) {
117       spvDiagnosticPrint(diagnostic);
118       spvDiagnosticDestroy(diagnostic);
119     }
120     EXPECT_EQ(SPV_SUCCESS, error) << txt;
121 
122     const std::string decoded_string = decoded_text->str;
123     spvTextDestroy(decoded_text);
124 
125     return decoded_string;
126   }
127 
128   // Encodes SPIR-V text into binary. This is expected to succeed.
129   // The given words are then appended to the binary, and the result
130   // is then decoded. This is expected to fail.
131   // Returns the error message.
EncodeSuccessfullyDecodeFailed(const std::string & txt,const SpirvVector & words_to_append)132   std::string EncodeSuccessfullyDecodeFailed(
133       const std::string& txt, const SpirvVector& words_to_append) {
134     DestroyBinary();
135     DestroyDiagnostic();
136     SpirvVector code =
137         spvtest::Concatenate({CompileSuccessfully(txt), words_to_append});
138 
139     spv_text decoded_text;
140     EXPECT_NE(SPV_SUCCESS,
141               spvBinaryToText(ScopedContext().context, code.data(), code.size(),
142                               SPV_BINARY_TO_TEXT_OPTION_NONE, &decoded_text,
143                               &diagnostic));
144     if (diagnostic) {
145       std::string error_message = diagnostic->error;
146       spvDiagnosticDestroy(diagnostic);
147       diagnostic = nullptr;
148       return error_message;
149     }
150     return "";
151   }
152 
153   // Compiles SPIR-V text, asserts success, and returns the words representing
154   // the instructions.  In particular, skip the words in the SPIR-V header.
155   SpirvVector CompiledInstructions(const std::string& txt,
156                                    spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
157     const SpirvVector code = CompileSuccessfully(txt, env);
158     SpirvVector result;
159     // Extract just the instructions.
160     // If the code fails to compile, then return the empty vector.
161     // In any case, don't crash or invoke undefined behaviour.
162     if (code.size() >= kFirstInstruction)
163       result = Subvector(code, kFirstInstruction);
164     return result;
165   }
166 
SetText(const std::string & code)167   void SetText(const std::string& code) {
168     textString = code;
169     text.str = textString.c_str();
170     text.length = textString.size();
171   }
172 
173   // Destroys the binary, if it exists.
DestroyBinary()174   void DestroyBinary() {
175     spvBinaryDestroy(binary);
176     binary = nullptr;
177   }
178 
179   // Destroys the diagnostic, if it exists.
DestroyDiagnostic()180   void DestroyDiagnostic() {
181     spvDiagnosticDestroy(diagnostic);
182     diagnostic = nullptr;
183   }
184 
185   spv_diagnostic diagnostic;
186 
187   std::string textString;
188   spv_text_t text;
189   spv_binary binary;
190 };
191 
192 using TextToBinaryTest = TextToBinaryTestBase<::testing::Test>;
193 }  // namespace spvtest
194 
195 using RoundTripTest =
196     spvtest::TextToBinaryTestBase<::testing::TestWithParam<std::string>>;
197 
198 #endif  // TEST_TEST_FIXTURE_H_
199