]> git.tdb.fi Git - libs/gl.git/blob - tests/glsl/glslcompiler.cpp
Refactor the GLSL compiler test cases with a helper class
[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 using namespace std;
47 using namespace Msp;
48
49 void GlslCompilerHelper::load_all_test_cases(const FS::Path &tests_dir)
50 {
51         list<string> test_files = FS::list_filtered(tests_dir, "\\.glsl$");
52         test_files.sort();
53         for(const auto &fn: test_files)
54                 load_test_case((tests_dir/fn).str());
55 }
56
57 const GlslCompilerHelper::TestCase &GlslCompilerHelper::load_test_case(const string &fn)
58 {
59         IO::BufferedFile file(fn);
60         TestCase test_case;
61         test_case.name = FS::basename(fn);
62         test_case.compile_mode = GL::SL::Compiler::PROGRAM;
63         string *target = &test_case.source;
64         while(!file.eof())
65         {
66                 string line;
67                 if(!file.getline(line))
68                         break;
69
70                 if(line=="*/")
71                         continue;
72
73                 string::size_type pos = line.find("Expected output:");
74                 if(pos!=string::npos)
75                 {
76                         string stage = strip(line.substr(pos+16));
77                         if(stage=="vertex")
78                                 target = &test_case.expected_output[GL::SL::Stage::VERTEX];
79                         else if(stage=="geometry")
80                                 target = &test_case.expected_output[GL::SL::Stage::GEOMETRY];
81                         else if(stage=="fragment")
82                                 target = &test_case.expected_output[GL::SL::Stage::FRAGMENT];
83                         else
84                                 throw runtime_error("Unknown stage "+stage);
85                         continue;
86                 }
87
88                 pos = line.find("Expected error:");
89                 if(pos!=string::npos)
90                 {
91                         target = &test_case.expected_error;
92                         continue;
93                 }
94
95                 pos = line.find("Compile mode:");
96                 if(pos!=string::npos)
97                 {
98                         string mode = strip(line.substr(pos+13));
99                         if(mode=="module")
100                                 test_case.compile_mode = GL::SL::Compiler::MODULE;
101                         else if(mode=="program")
102                                 test_case.compile_mode = GL::SL::Compiler::PROGRAM;
103                         else
104                                 throw runtime_error("Unknown compile mode "+mode);
105                         continue;
106                 }
107
108                 pos = line.find("Specialize:");
109                 if(pos!=string::npos)
110                 {
111                         vector<string> parts = split(line.substr(pos+11));
112                         int value = 0;
113                         if(parts[1]=="true")
114                                 value = 1;
115                         else if(parts[1]=="false")
116                                 value = 0;
117                         else
118                                 value = lexical_cast<int>(parts[1]);
119                         test_case.spec_values[parts[0]] = value;
120                         continue;
121                 }
122
123                 *target += line;
124                 *target += '\n';
125         }
126         test_cases.push_back(test_case);
127
128         return test_cases.back();
129 }
130
131 void GlslCompilerHelper::verify_output(const string &output, const string &expected)
132 {
133         GL::SL::Tokenizer tokenizer;
134         tokenizer.begin(output, "<output>");
135
136         GL::SL::Tokenizer expected_tkn;
137         expected_tkn.begin(expected, "<expected>");
138
139         while(1)
140         {
141                 string token = expected_tkn.parse_token();
142
143                 try
144                 {
145                         tokenizer.expect(token);
146                 }
147                 catch(const GL::SL::invalid_shader_source &exc)
148                 {
149                         fail(exc.what());
150                 }
151
152                 if(token.empty())
153                         break;
154         }
155 }
156
157 void GlslCompilerHelper::verify_error(const string &output, const string &expected)
158 {
159         auto i = output.begin();
160         auto j = expected.begin();
161         bool space = true;
162         while(i!=output.end() && j!=expected.end())
163         {
164                 if(*i==*j)
165                 {
166                         ++i;
167                         ++j;
168                 }
169                 else if(isspace(*i) && isspace(*j))
170                 {
171                         ++i;
172                         ++j;
173                         space = true;
174                 }
175                 else if(space && isspace(*i))
176                         ++i;
177                 else if(space && isspace(*j))
178                         ++j;
179                 else
180                 {
181                         string out_line = extract_line(output, i);
182                         string expect_line = extract_line(expected, j);
183                         fail(format("Incorrect error line:\n%s\nExpected:\n%s", out_line, expect_line));
184                 }
185         }
186
187         while(i!=output.end() && isspace(*i))
188                 ++i;
189         while(j!=expected.end() && isspace(*j))
190                 ++j;
191
192         if(i!=output.end())
193                 fail(format("Extra error line: %s", extract_line(output, i)));
194         if(j!=expected.end())
195                 fail(format("Missing error line: %s", extract_line(expected, j)));
196 }
197
198 string GlslCompilerHelper::extract_line(const string &text, const string::const_iterator &iter)
199 {
200         string::const_iterator begin = iter;
201         for(; (begin!=text.begin() && *begin!='\n'); --begin) ;
202         if(*begin=='\n')
203                 ++begin;
204         string::const_iterator end = iter;
205         for(; (end!=text.end() && *end!='\n'); ++end) ;
206         return string(begin, end);
207 }
208
209
210 GlslCompilerTest::GlslCompilerTest()
211 {
212         load_all_test_cases("glsl");
213         for(const auto &tc: test_cases)
214                 add(&GlslCompilerTest::run_test_case, &tc, tc.name);
215 }
216
217 void GlslCompilerTest::run_test_case(const TestCase *test_case)
218 {
219         GL::SL::Compiler compiler(GL::SL::Features::all());
220         try
221         {
222                 compiler.set_source(test_case->source, "<test>");
223                 if(test_case->compile_mode==GL::SL::Compiler::PROGRAM)
224                         compiler.specialize(test_case->spec_values);
225                 compiler.compile(test_case->compile_mode);
226         }
227         catch(const GL::SL::invalid_shader_source &exc)
228         {
229                 if(!test_case->expected_error.empty())
230                 {
231                         debug("Errors from compile:");
232                         debug(exc.what());
233                         verify_error(exc.what(), test_case->expected_error);
234                         return;
235                 }
236                 throw;
237         }
238
239         if(!test_case->expected_error.empty())
240                 fail("Error expected but none thrown");
241
242         auto stages = compiler.get_stages();
243         for(auto s: stages)
244         {
245                 auto i = test_case->expected_output.find(s);
246                 if(i==test_case->expected_output.end())
247                         fail(format("Compiler produced extra stage %s", GL::SL::Stage::get_stage_name(s)));
248
249                 string output = compiler.get_stage_glsl(s);
250                 debug(format("Output for stage %s:", GL::SL::Stage::get_stage_name(s)));
251                 auto lines = split_fields(output, '\n');
252                 for(unsigned j=0; j<lines.size(); ++j)
253                         debug(format("%3d: %s", j+1, lines[j]));
254
255                 verify_output(output, i->second);
256         }
257
258         for(const auto &s: test_case->expected_output)
259                 if(find(stages, s.first)==stages.end())
260                         fail(format("Compiler didn't produce stage %s", GL::SL::Stage::get_stage_name(s.first)));
261 }