]> git.tdb.fi Git - libs/gl.git/blob - tests/glsl/glslcompiler.cpp
Support compile modes and specialization constants in the test harness
[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 #include <msp/io/print.h>
11
12 class GlslCompilerTest: public Msp::Test::RegisteredTest<GlslCompilerTest>
13 {
14 private:
15         struct TestCase
16         {
17                 std::string name;
18                 std::string source;
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;
23         };
24
25         std::list<TestCase> test_cases;
26
27 public:
28         GlslCompilerTest();
29
30         static const char *get_name() { return "GLSL compiler"; }
31
32 private:
33         const TestCase &load_test_case(const std::string &);
34
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 &);
39 };
40
41 using namespace std;
42 using namespace Msp;
43
44 GlslCompilerTest::GlslCompilerTest()
45 {
46         FS::Path tests_dir = "glsl";
47         list<string> test_files = FS::list_filtered(tests_dir, "\\.glsl$");
48         test_files.sort();
49         for(const auto &fn: test_files)
50                 load_test_case((tests_dir/fn).str());
51
52         for(const auto &tc: test_cases)
53                 add(&GlslCompilerTest::run_test_case, &tc, tc.name);
54 }
55
56 const GlslCompilerTest::TestCase &GlslCompilerTest::load_test_case(const string &fn)
57 {
58         IO::BufferedFile file(fn);
59         TestCase test_case;
60         test_case.name = FS::basename(fn);
61         test_case.compile_mode = GL::SL::Compiler::PROGRAM;
62         string *target = &test_case.source;
63         while(!file.eof())
64         {
65                 string line;
66                 if(!file.getline(line))
67                         break;
68
69                 if(line=="*/")
70                         continue;
71
72                 string::size_type pos = line.find("Expected output:");
73                 if(pos!=string::npos)
74                 {
75                         string stage = strip(line.substr(pos+16));
76                         if(stage=="vertex")
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];
82                         else
83                                 throw runtime_error("Unknown stage "+stage);
84                         continue;
85                 }
86
87                 pos = line.find("Expected error:");
88                 if(pos!=string::npos)
89                 {
90                         target = &test_case.expected_error;
91                         continue;
92                 }
93
94                 pos = line.find("Compile mode:");
95                 if(pos!=string::npos)
96                 {
97                         string mode = strip(line.substr(pos+13));
98                         if(mode=="module")
99                                 test_case.compile_mode = GL::SL::Compiler::MODULE;
100                         else if(mode=="program")
101                                 test_case.compile_mode = GL::SL::Compiler::PROGRAM;
102                         else
103                                 throw runtime_error("Unknown compile mode "+mode);
104                         continue;
105                 }
106
107                 pos = line.find("Specialize:");
108                 if(pos!=string::npos)
109                 {
110                         vector<string> parts = split(line.substr(pos+11));
111                         int value = 0;
112                         if(parts[1]=="true")
113                                 value = 1;
114                         else if(parts[1]=="false")
115                                 value = 0;
116                         else
117                                 value = lexical_cast<int>(parts[1]);
118                         test_case.spec_values[parts[0]] = value;
119                         continue;
120                 }
121
122                 *target += line;
123                 *target += '\n';
124         }
125         test_cases.push_back(test_case);
126
127         return test_cases.back();
128 }
129
130 void GlslCompilerTest::run_test_case(const TestCase *test_case)
131 {
132         GL::SL::Compiler compiler(GL::SL::Features::all());
133         try
134         {
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);
139         }
140         catch(const GL::SL::invalid_shader_source &exc)
141         {
142                 if(!test_case->expected_error.empty())
143                 {
144                         debug("Errors from compile:");
145                         debug(exc.what());
146                         verify_error(exc.what(), test_case->expected_error);
147                         return;
148                 }
149                 throw;
150         }
151
152         if(!test_case->expected_error.empty())
153                 fail("Error expected but none thrown");
154
155         auto stages = compiler.get_stages();
156         for(auto s: stages)
157         {
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)));
161
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]));
167
168                 verify_output(output, i->second);
169         }
170
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)));
174 }
175
176 void GlslCompilerTest::verify_output(const string &output, const string &expected)
177 {
178         GL::SL::Tokenizer tokenizer;
179         tokenizer.begin(output, "<output>");
180
181         GL::SL::Tokenizer expected_tkn;
182         expected_tkn.begin(expected, "<expected>");
183
184         while(1)
185         {
186                 string token = expected_tkn.parse_token();
187
188                 try
189                 {
190                         tokenizer.expect(token);
191                 }
192                 catch(const GL::SL::invalid_shader_source &exc)
193                 {
194                         fail(exc.what());
195                 }
196
197                 if(token.empty())
198                         break;
199         }
200 }
201
202 void GlslCompilerTest::verify_error(const string &output, const string &expected)
203 {
204         auto i = output.begin();
205         auto j = expected.begin();
206         bool space = true;
207         while(i!=output.end() && j!=expected.end())
208         {
209                 if(*i==*j)
210                 {
211                         ++i;
212                         ++j;
213                 }
214                 else if(isspace(*i) && isspace(*j))
215                 {
216                         ++i;
217                         ++j;
218                         space = true;
219                 }
220                 else if(space && isspace(*i))
221                         ++i;
222                 else if(space && isspace(*j))
223                         ++j;
224                 else
225                 {
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));
229                 }
230         }
231
232         while(i!=output.end() && isspace(*i))
233                 ++i;
234         while(j!=expected.end() && isspace(*j))
235                 ++j;
236
237         if(i!=output.end())
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)));
241 }
242
243 string GlslCompilerTest::extract_line(const string &text, const string::const_iterator &iter)
244 {
245         string::const_iterator begin = iter;
246         for(; (begin!=text.begin() && *begin!='\n'); --begin) ;
247         if(*begin=='\n')
248                 ++begin;
249         string::const_iterator end = iter;
250         for(; (end!=text.end() && *end!='\n'); ++end) ;
251         return string(begin, end);
252 }