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>
10 class GlslCompilerHelper
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;
23 TestCase(): expect_success(true) { }
26 std::list<TestCase> test_cases;
28 void load_all_test_cases(const Msp::FS::Path &);
29 const TestCase &load_test_case(const std::string &);
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;
37 class GlslCompilerTest: public Msp::Test::RegisteredTest<GlslCompilerTest>, private GlslCompilerHelper
42 static const char *get_name() { return "GLSL compiler"; }
45 void run_test_case(const TestCase *);
46 virtual void fail(const std::string &m) { Test::fail(m); }
49 class GlslCompilerIdempotence: public Msp::Test::RegisteredTest<GlslCompilerIdempotence>, private GlslCompilerHelper
52 GlslCompilerIdempotence();
54 static const char *get_name() { return "GLSL compiler idempotence"; }
57 void run_test_case(const TestCase *);
58 virtual void fail(const std::string &m) { Test::fail(m); }
64 void GlslCompilerHelper::load_all_test_cases(const FS::Path &tests_dir)
66 list<string> test_files = FS::list_filtered(tests_dir, "\\.glsl$");
68 for(const auto &fn: test_files)
69 load_test_case((tests_dir/fn).str());
72 const GlslCompilerHelper::TestCase &GlslCompilerHelper::load_test_case(const string &fn)
74 IO::BufferedFile file(fn);
76 test_case.name = FS::basename(fn);
77 test_case.compile_mode = GL::SL::Compiler::PROGRAM;
78 string *target = &test_case.source;
82 if(!file.getline(line))
88 string::size_type pos = line.find("Expected output:");
91 string stage = strip(line.substr(pos+16));
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];
99 throw runtime_error("Unknown stage "+stage);
103 pos = line.find("Expected error:");
104 if(pos==string::npos)
105 pos = line.find("Expected diagnostic:");
106 if(pos!=string::npos)
108 target = &test_case.expected_diagnostic;
109 test_case.expect_success = (line[pos+9]!='e');
113 pos = line.find("Compile mode:");
114 if(pos!=string::npos)
116 string mode = strip(line.substr(pos+13));
118 test_case.compile_mode = GL::SL::Compiler::MODULE;
119 else if(mode=="program")
120 test_case.compile_mode = GL::SL::Compiler::PROGRAM;
122 throw runtime_error("Unknown compile mode "+mode);
126 pos = line.find("Specialize:");
127 if(pos!=string::npos)
129 vector<string> parts = split(line.substr(pos+11));
133 else if(parts[1]=="false")
136 value = lexical_cast<int>(parts[1]);
137 test_case.spec_values[parts[0]] = value;
144 test_cases.push_back(test_case);
146 return test_cases.back();
149 void GlslCompilerHelper::verify_output(const string &output, const string &expected)
151 GL::SL::Tokenizer tokenizer;
152 tokenizer.begin(output, "<output>");
154 GL::SL::Tokenizer expected_tkn;
155 expected_tkn.begin(expected, "<expected>");
159 string token = expected_tkn.parse_token();
163 tokenizer.expect(token);
165 catch(const GL::SL::invalid_shader_source &exc)
175 void GlslCompilerHelper::verify_diagnostic(const string &output, const string &expected)
177 auto i = output.begin();
178 auto j = expected.begin();
180 while(i!=output.end() && j!=expected.end())
187 else if(isspace(*i) && isspace(*j))
193 else if(space && isspace(*i))
195 else if(space && isspace(*j))
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));
205 while(i!=output.end() && isspace(*i))
207 while(j!=expected.end() && isspace(*j))
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)));
216 string GlslCompilerHelper::extract_line(const string &text, const string::const_iterator &iter)
218 string::const_iterator begin = iter;
219 for(; (begin!=text.begin() && *begin!='\n'); --begin) ;
222 string::const_iterator end = iter;
223 for(; (end!=text.end() && *end!='\n'); ++end) ;
224 return string(begin, end);
228 GlslCompilerTest::GlslCompilerTest()
230 load_all_test_cases("glsl");
231 for(const auto &tc: test_cases)
232 add(&GlslCompilerTest::run_test_case, &tc, tc.name);
235 void GlslCompilerTest::run_test_case(const TestCase *test_case)
237 GL::SL::Compiler compiler(GL::SL::Features::latest());
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);
245 catch(const GL::SL::invalid_shader_source &exc)
247 if(!test_case->expect_success)
249 debug("Errors from compile:");
251 verify_diagnostic(exc.what(), test_case->expected_diagnostic);
257 if(!test_case->expect_success)
258 fail("Error expected but none thrown");
260 verify_diagnostic(compiler.get_diagnostics(), test_case->expected_diagnostic);
262 auto stages = compiler.get_stages();
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)));
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]));
275 verify_output(output, i->second);
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)));
284 GlslCompilerIdempotence::GlslCompilerIdempotence()
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);
292 void GlslCompilerIdempotence::run_test_case(const TestCase *test_case)
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);
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);
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)
310 string output = compiler.get_stage_glsl(*i);
311 string output2 = compiler2.get_stage_glsl(*j);
313 verify_output(output2, output);
317 fail(format("Second pass didn't produce stage %s", GL::SL::Stage::get_stage_name(*i)));
319 fail(format("Second pass produced extra stage %s", GL::SL::Stage::get_stage_name(*j)));