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