]> git.tdb.fi Git - libs/gl.git/blob - tests/glsl/glslcompiler.cpp
Make the GLSL compiler test runner able to verify non-error diagnostics
[libs/gl.git] / tests / glsl / glslcompiler.cpp
1 #include <msp/core/algorithm.h>
2 #include <msp/fs/dir.h>
3 #include <msp/fs/utils.h>
4 #include <msp/gl/glsl/compiler.h>
5 #include <msp/gl/glsl/glsl_error.h>
6 #include <msp/gl/glsl/tokenizer.h>
7 #include <msp/strings/utils.h>
8 #include <msp/test/test.h>
9
10 class GlslCompilerHelper
11 {
12 protected:
13         struct TestCase
14         {
15                 std::string name;
16                 std::string source;
17                 Msp::GL::SL::Compiler::Mode compile_mode;
18                 std::map<std::string, int> spec_values;
19                 std::map<Msp::GL::SL::Stage::Type, std::string> expected_output;
20                 std::string expected_diagnostic;
21                 bool expect_success;
22
23                 TestCase(): expect_success(true) { }
24         };
25
26         std::list<TestCase> test_cases;
27
28         void load_all_test_cases(const Msp::FS::Path &);
29         const TestCase &load_test_case(const std::string &);
30
31         void verify_output(const std::string &, const std::string &);
32         void verify_diagnostic(const std::string &, const std::string &);
33         std::string extract_line(const std::string &, const std::string::const_iterator &);
34         virtual void fail(const std::string &) = 0;
35 };
36
37 class GlslCompilerTest: public Msp::Test::RegisteredTest<GlslCompilerTest>, private GlslCompilerHelper
38 {
39 public:
40         GlslCompilerTest();
41
42         static const char *get_name() { return "GLSL compiler"; }
43
44 private:
45         void run_test_case(const TestCase *);
46         virtual void fail(const std::string &m) { Test::fail(m); }
47 };
48
49 class GlslCompilerIdempotence: public Msp::Test::RegisteredTest<GlslCompilerIdempotence>, private GlslCompilerHelper
50 {
51 public:
52         GlslCompilerIdempotence();
53
54         static const char *get_name() { return "GLSL compiler idempotence"; }
55
56 private:
57         void run_test_case(const TestCase *);
58         virtual void fail(const std::string &m) { Test::fail(m); }
59 };
60
61 using namespace std;
62 using namespace Msp;
63
64 void GlslCompilerHelper::load_all_test_cases(const FS::Path &tests_dir)
65 {
66         list<string> test_files = FS::list_filtered(tests_dir, "\\.glsl$");
67         test_files.sort();
68         for(const auto &fn: test_files)
69                 load_test_case((tests_dir/fn).str());
70 }
71
72 const GlslCompilerHelper::TestCase &GlslCompilerHelper::load_test_case(const string &fn)
73 {
74         IO::BufferedFile file(fn);
75         TestCase test_case;
76         test_case.name = FS::basename(fn);
77         test_case.compile_mode = GL::SL::Compiler::PROGRAM;
78         string *target = &test_case.source;
79         while(!file.eof())
80         {
81                 string line;
82                 if(!file.getline(line))
83                         break;
84
85                 if(line=="*/")
86                         continue;
87
88                 string::size_type pos = line.find("Expected output:");
89                 if(pos!=string::npos)
90                 {
91                         string stage = strip(line.substr(pos+16));
92                         if(stage=="vertex")
93                                 target = &test_case.expected_output[GL::SL::Stage::VERTEX];
94                         else if(stage=="geometry")
95                                 target = &test_case.expected_output[GL::SL::Stage::GEOMETRY];
96                         else if(stage=="fragment")
97                                 target = &test_case.expected_output[GL::SL::Stage::FRAGMENT];
98                         else
99                                 throw runtime_error("Unknown stage "+stage);
100                         continue;
101                 }
102
103                 pos = line.find("Expected error:");
104                 if(pos==string::npos)
105                         pos = line.find("Expected diagnostic:");
106                 if(pos!=string::npos)
107                 {
108                         target = &test_case.expected_diagnostic;
109                         test_case.expect_success = (line[pos+9]!='e');
110                         continue;
111                 }
112
113                 pos = line.find("Compile mode:");
114                 if(pos!=string::npos)
115                 {
116                         string mode = strip(line.substr(pos+13));
117                         if(mode=="module")
118                                 test_case.compile_mode = GL::SL::Compiler::MODULE;
119                         else if(mode=="program")
120                                 test_case.compile_mode = GL::SL::Compiler::PROGRAM;
121                         else
122                                 throw runtime_error("Unknown compile mode "+mode);
123                         continue;
124                 }
125
126                 pos = line.find("Specialize:");
127                 if(pos!=string::npos)
128                 {
129                         vector<string> parts = split(line.substr(pos+11));
130                         int value = 0;
131                         if(parts[1]=="true")
132                                 value = 1;
133                         else if(parts[1]=="false")
134                                 value = 0;
135                         else
136                                 value = lexical_cast<int>(parts[1]);
137                         test_case.spec_values[parts[0]] = value;
138                         continue;
139                 }
140
141                 *target += line;
142                 *target += '\n';
143         }
144         test_cases.push_back(test_case);
145
146         return test_cases.back();
147 }
148
149 void GlslCompilerHelper::verify_output(const string &output, const string &expected)
150 {
151         GL::SL::Tokenizer tokenizer;
152         tokenizer.begin(output, "<output>");
153
154         GL::SL::Tokenizer expected_tkn;
155         expected_tkn.begin(expected, "<expected>");
156
157         while(1)
158         {
159                 string token = expected_tkn.parse_token();
160
161                 try
162                 {
163                         tokenizer.expect(token);
164                 }
165                 catch(const GL::SL::invalid_shader_source &exc)
166                 {
167                         fail(exc.what());
168                 }
169
170                 if(token.empty())
171                         break;
172         }
173 }
174
175 void GlslCompilerHelper::verify_diagnostic(const string &output, const string &expected)
176 {
177         auto i = output.begin();
178         auto j = expected.begin();
179         bool space = true;
180         while(i!=output.end() && j!=expected.end())
181         {
182                 if(*i==*j)
183                 {
184                         ++i;
185                         ++j;
186                 }
187                 else if(isspace(*i) && isspace(*j))
188                 {
189                         ++i;
190                         ++j;
191                         space = true;
192                 }
193                 else if(space && isspace(*i))
194                         ++i;
195                 else if(space && isspace(*j))
196                         ++j;
197                 else
198                 {
199                         string out_line = extract_line(output, i);
200                         string expect_line = extract_line(expected, j);
201                         fail(format("Incorrect diagnostic line:\n%s\nExpected:\n%s", out_line, expect_line));
202                 }
203         }
204
205         while(i!=output.end() && isspace(*i))
206                 ++i;
207         while(j!=expected.end() && isspace(*j))
208                 ++j;
209
210         if(i!=output.end())
211                 fail(format("Extra diagnostic line: %s", extract_line(output, i)));
212         if(j!=expected.end())
213                 fail(format("Missing diagnostic line: %s", extract_line(expected, j)));
214 }
215
216 string GlslCompilerHelper::extract_line(const string &text, const string::const_iterator &iter)
217 {
218         string::const_iterator begin = iter;
219         for(; (begin!=text.begin() && *begin!='\n'); --begin) ;
220         if(*begin=='\n')
221                 ++begin;
222         string::const_iterator end = iter;
223         for(; (end!=text.end() && *end!='\n'); ++end) ;
224         return string(begin, end);
225 }
226
227
228 GlslCompilerTest::GlslCompilerTest()
229 {
230         load_all_test_cases("glsl");
231         for(const auto &tc: test_cases)
232                 add(&GlslCompilerTest::run_test_case, &tc, tc.name);
233 }
234
235 void GlslCompilerTest::run_test_case(const TestCase *test_case)
236 {
237         GL::SL::Compiler compiler(GL::SL::Features::latest());
238         try
239         {
240                 compiler.set_source(test_case->source, "<test>");
241                 if(test_case->compile_mode==GL::SL::Compiler::PROGRAM)
242                         compiler.specialize(test_case->spec_values);
243                 compiler.compile(test_case->compile_mode);
244         }
245         catch(const GL::SL::invalid_shader_source &exc)
246         {
247                 if(!test_case->expect_success)
248                 {
249                         debug("Errors from compile:");
250                         debug(exc.what());
251                         verify_diagnostic(exc.what(), test_case->expected_diagnostic);
252                         return;
253                 }
254                 throw;
255         }
256
257         if(!test_case->expect_success)
258                 fail("Error expected but none thrown");
259
260         verify_diagnostic(compiler.get_diagnostics(), test_case->expected_diagnostic);
261
262         auto stages = compiler.get_stages();
263         for(auto s: stages)
264         {
265                 auto i = test_case->expected_output.find(s);
266                 if(i==test_case->expected_output.end())
267                         fail(format("Compiler produced extra stage %s", GL::SL::Stage::get_stage_name(s)));
268
269                 string output = compiler.get_stage_glsl(s);
270                 debug(format("Output for stage %s:", GL::SL::Stage::get_stage_name(s)));
271                 auto lines = split_fields(output, '\n');
272                 for(unsigned j=0; j<lines.size(); ++j)
273                         debug(format("%3d: %s", j+1, lines[j]));
274
275                 verify_output(output, i->second);
276         }
277
278         for(const auto &s: test_case->expected_output)
279                 if(find(stages, s.first)==stages.end())
280                         fail(format("Compiler didn't produce stage %s", GL::SL::Stage::get_stage_name(s.first)));
281 }
282
283
284 GlslCompilerIdempotence::GlslCompilerIdempotence()
285 {
286         load_all_test_cases("glsl");
287         for(const auto &tc: test_cases)
288                 if(tc.expect_success)
289                         add(&GlslCompilerIdempotence::run_test_case, &tc, tc.name);
290 }
291
292 void GlslCompilerIdempotence::run_test_case(const TestCase *test_case)
293 {
294         GL::SL::Compiler compiler(GL::SL::Features::latest());
295         compiler.set_source(test_case->source, "<test>");
296         if(test_case->compile_mode==GL::SL::Compiler::PROGRAM)
297                 compiler.specialize(test_case->spec_values);
298         compiler.compile(test_case->compile_mode);
299
300         GL::SL::Compiler compiler2(GL::SL::Features::latest());
301         compiler2.set_source(compiler.get_combined_glsl(), "<loopback>");
302         compiler2.compile(test_case->compile_mode);
303
304         auto stages = compiler.get_stages();
305         auto stages2 = compiler2.get_stages();
306         auto i = stages.begin();
307         auto j = stages2.begin();
308         for(; (i!=stages.end() && j!=stages2.end() && *i==*j); ++i, ++j)
309         {
310                 string output = compiler.get_stage_glsl(*i);
311                 string output2 = compiler2.get_stage_glsl(*j);
312
313                 verify_output(output2, output);
314         }
315
316         if(i!=stages.end())
317                 fail(format("Second pass didn't produce stage %s", GL::SL::Stage::get_stage_name(*i)));
318         if(j!=stages2.end())
319                 fail(format("Second pass produced extra stage %s", GL::SL::Stage::get_stage_name(*j)));
320 }