]> git.tdb.fi Git - libs/gl.git/blob - tests/glsl/glslcompiler.cpp
Make the GLSL compiler tester able to verify errors
[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                 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 public:
26         GlslCompilerTest();
27
28         static const char *get_name() { return "GLSL compiler"; }
29
30 private:
31         const TestCase &load_test_case(const std::string &);
32
33         void run_test_case(const TestCase *);
34         void verify_output(const std::string &, const std::string &);
35         void verify_error(const std::string &, const std::string &);
36         std::string extract_line(const std::string &, const std::string::const_iterator &);
37 };
38
39 using namespace std;
40 using namespace Msp;
41
42 GlslCompilerTest::GlslCompilerTest()
43 {
44         FS::Path tests_dir = "glsl";
45         list<string> test_files = FS::list_filtered(tests_dir, "\\.glsl$");
46         test_files.sort();
47         for(const auto &fn: test_files)
48                 load_test_case((tests_dir/fn).str());
49
50         for(const auto &tc: test_cases)
51                 add(&GlslCompilerTest::run_test_case, &tc, tc.name);
52 }
53
54 const GlslCompilerTest::TestCase &GlslCompilerTest::load_test_case(const string &fn)
55 {
56         IO::BufferedFile file(fn);
57         TestCase test_case;
58         test_case.name = FS::basename(fn);
59         string *target = &test_case.source;
60         while(!file.eof())
61         {
62                 string line;
63                 if(!file.getline(line))
64                         break;
65
66                 if(line=="*/")
67                         continue;
68
69                 string::size_type expected = line.find("Expected output:");
70                 if(expected!=string::npos)
71                 {
72                         string stage = strip(line.substr(expected+16));
73                         if(stage=="vertex")
74                                 target = &test_case.expected_output[GL::SL::Stage::VERTEX];
75                         else if(stage=="geometry")
76                                 target = &test_case.expected_output[GL::SL::Stage::GEOMETRY];
77                         else if(stage=="fragment")
78                                 target = &test_case.expected_output[GL::SL::Stage::FRAGMENT];
79                         else
80                                 throw runtime_error("Unknown stage "+stage);
81                         continue;
82                 }
83
84                 expected = line.find("Expected error:");
85                 if(expected!=string::npos)
86                 {
87                         target = &test_case.expected_error;
88                         continue;
89                 }
90
91                 *target += line;
92                 *target += '\n';
93         }
94         test_cases.push_back(test_case);
95
96         return test_cases.back();
97 }
98
99 void GlslCompilerTest::run_test_case(const TestCase *test_case)
100 {
101         GL::SL::Compiler compiler(GL::SL::Features::all());
102         compiler.set_source(test_case->source, "<test>");
103         try
104         {
105                 compiler.compile(GL::SL::Compiler::PROGRAM);
106         }
107         catch(const GL::SL::invalid_shader_source &exc)
108         {
109                 if(!test_case->expected_error.empty())
110                 {
111                         debug("Errors from compile:");
112                         debug(exc.what());
113                         verify_error(exc.what(), test_case->expected_error);
114                         return;
115                 }
116                 throw;
117         }
118
119         auto stages = compiler.get_stages();
120         for(auto s: stages)
121         {
122                 auto i = test_case->expected_output.find(s);
123                 if(i==test_case->expected_output.end())
124                         fail(format("Compiler produced extra stage %s", GL::SL::Stage::get_stage_name(s)));
125
126                 string output = compiler.get_stage_glsl(s);
127                 debug(format("Output for stage %s:", GL::SL::Stage::get_stage_name(s)));
128                 auto lines = split_fields(output, '\n');
129                 for(unsigned j=0; j<lines.size(); ++j)
130                         debug(format("%3d: %s", j+1, lines[j]));
131
132                 verify_output(output, i->second);
133         }
134
135         for(const auto &s: test_case->expected_output)
136                 if(find(stages, s.first)==stages.end())
137                         fail(format("Compiler didn't produce stage %s", GL::SL::Stage::get_stage_name(s.first)));
138 }
139
140 void GlslCompilerTest::verify_output(const string &output, const string &expected)
141 {
142         GL::SL::Tokenizer tokenizer;
143         tokenizer.begin(output, "<output>");
144
145         GL::SL::Tokenizer expected_tkn;
146         expected_tkn.begin(expected, "<expected>");
147
148         while(1)
149         {
150                 string token = expected_tkn.parse_token();
151
152                 try
153                 {
154                         tokenizer.expect(token);
155                 }
156                 catch(const GL::SL::invalid_shader_source &exc)
157                 {
158                         fail(exc.what());
159                 }
160
161                 if(token.empty())
162                         break;
163         }
164 }
165
166 void GlslCompilerTest::verify_error(const string &output, const string &expected)
167 {
168         auto i = output.begin();
169         auto j = expected.begin();
170         bool space = true;
171         while(i!=output.end() && j!=expected.end())
172         {
173                 if(*i==*j)
174                 {
175                         ++i;
176                         ++j;
177                 }
178                 else if(isspace(*i) && isspace(*j))
179                 {
180                         ++i;
181                         ++j;
182                         space = true;
183                 }
184                 else if(space && isspace(*i))
185                         ++i;
186                 else if(space && isspace(*j))
187                         ++j;
188                 else
189                 {
190                         string out_line = extract_line(output, i);
191                         string expect_line = extract_line(expected, j);
192                         fail(format("Incorrect error line:\n%s\nExpected:\n%s", out_line, expect_line));
193                 }
194         }
195
196         while(i!=output.end() && isspace(*i))
197                 ++i;
198         while(j!=expected.end() && isspace(*j))
199                 ++j;
200 }
201
202 string GlslCompilerTest::extract_line(const string &text, const string::const_iterator &iter)
203 {
204         string::const_iterator begin = iter;
205         for(; (begin!=text.begin() && *begin!='\n'); --begin) ;
206         if(*begin=='\n')
207                 ++begin;
208         string::const_iterator end = iter;
209         for(; (end!=text.end() && *end!='\n'); ++end) ;
210         return string(begin, end);
211 }