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 #include <msp/io/print.h>
12 class GlslCompilerTest: public Msp::Test::RegisteredTest<GlslCompilerTest>
19 Msp::GL::SL::Compiler::Mode compile_mode;
20 std::map<std::string, int> spec_values;
21 std::map<Msp::GL::SL::Stage::Type, std::string> expected_output;
22 std::string expected_error;
25 std::list<TestCase> test_cases;
30 static const char *get_name() { return "GLSL compiler"; }
33 const TestCase &load_test_case(const std::string &);
35 void run_test_case(const TestCase *);
36 void verify_output(const std::string &, const std::string &);
37 void verify_error(const std::string &, const std::string &);
38 std::string extract_line(const std::string &, const std::string::const_iterator &);
44 GlslCompilerTest::GlslCompilerTest()
46 FS::Path tests_dir = "glsl";
47 list<string> test_files = FS::list_filtered(tests_dir, "\\.glsl$");
49 for(const auto &fn: test_files)
50 load_test_case((tests_dir/fn).str());
52 for(const auto &tc: test_cases)
53 add(&GlslCompilerTest::run_test_case, &tc, tc.name);
56 const GlslCompilerTest::TestCase &GlslCompilerTest::load_test_case(const string &fn)
58 IO::BufferedFile file(fn);
60 test_case.name = FS::basename(fn);
61 test_case.compile_mode = GL::SL::Compiler::PROGRAM;
62 string *target = &test_case.source;
66 if(!file.getline(line))
72 string::size_type pos = line.find("Expected output:");
75 string stage = strip(line.substr(pos+16));
77 target = &test_case.expected_output[GL::SL::Stage::VERTEX];
78 else if(stage=="geometry")
79 target = &test_case.expected_output[GL::SL::Stage::GEOMETRY];
80 else if(stage=="fragment")
81 target = &test_case.expected_output[GL::SL::Stage::FRAGMENT];
83 throw runtime_error("Unknown stage "+stage);
87 pos = line.find("Expected error:");
90 target = &test_case.expected_error;
94 pos = line.find("Compile mode:");
97 string mode = strip(line.substr(pos+13));
99 test_case.compile_mode = GL::SL::Compiler::MODULE;
100 else if(mode=="program")
101 test_case.compile_mode = GL::SL::Compiler::PROGRAM;
103 throw runtime_error("Unknown compile mode "+mode);
107 pos = line.find("Specialize:");
108 if(pos!=string::npos)
110 vector<string> parts = split(line.substr(pos+11));
114 else if(parts[1]=="false")
117 value = lexical_cast<int>(parts[1]);
118 test_case.spec_values[parts[0]] = value;
125 test_cases.push_back(test_case);
127 return test_cases.back();
130 void GlslCompilerTest::run_test_case(const TestCase *test_case)
132 GL::SL::Compiler compiler(GL::SL::Features::all());
135 compiler.set_source(test_case->source, "<test>");
136 if(test_case->compile_mode==GL::SL::Compiler::PROGRAM)
137 compiler.specialize(test_case->spec_values);
138 compiler.compile(test_case->compile_mode);
140 catch(const GL::SL::invalid_shader_source &exc)
142 if(!test_case->expected_error.empty())
144 debug("Errors from compile:");
146 verify_error(exc.what(), test_case->expected_error);
152 if(!test_case->expected_error.empty())
153 fail("Error expected but none thrown");
155 auto stages = compiler.get_stages();
158 auto i = test_case->expected_output.find(s);
159 if(i==test_case->expected_output.end())
160 fail(format("Compiler produced extra stage %s", GL::SL::Stage::get_stage_name(s)));
162 string output = compiler.get_stage_glsl(s);
163 debug(format("Output for stage %s:", GL::SL::Stage::get_stage_name(s)));
164 auto lines = split_fields(output, '\n');
165 for(unsigned j=0; j<lines.size(); ++j)
166 debug(format("%3d: %s", j+1, lines[j]));
168 verify_output(output, i->second);
171 for(const auto &s: test_case->expected_output)
172 if(find(stages, s.first)==stages.end())
173 fail(format("Compiler didn't produce stage %s", GL::SL::Stage::get_stage_name(s.first)));
176 void GlslCompilerTest::verify_output(const string &output, const string &expected)
178 GL::SL::Tokenizer tokenizer;
179 tokenizer.begin(output, "<output>");
181 GL::SL::Tokenizer expected_tkn;
182 expected_tkn.begin(expected, "<expected>");
186 string token = expected_tkn.parse_token();
190 tokenizer.expect(token);
192 catch(const GL::SL::invalid_shader_source &exc)
202 void GlslCompilerTest::verify_error(const string &output, const string &expected)
204 auto i = output.begin();
205 auto j = expected.begin();
207 while(i!=output.end() && j!=expected.end())
214 else if(isspace(*i) && isspace(*j))
220 else if(space && isspace(*i))
222 else if(space && isspace(*j))
226 string out_line = extract_line(output, i);
227 string expect_line = extract_line(expected, j);
228 fail(format("Incorrect error line:\n%s\nExpected:\n%s", out_line, expect_line));
232 while(i!=output.end() && isspace(*i))
234 while(j!=expected.end() && isspace(*j))
238 fail(format("Extra error line: %s", extract_line(output, i)));
239 if(j!=expected.end())
240 fail(format("Missing error line: %s", extract_line(expected, j)));
243 string GlslCompilerTest::extract_line(const string &text, const string::const_iterator &iter)
245 string::const_iterator begin = iter;
246 for(; (begin!=text.begin() && *begin!='\n'); --begin) ;
249 string::const_iterator end = iter;
250 for(; (end!=text.end() && *end!='\n'); ++end) ;
251 return string(begin, end);