1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "gpu/config/gpu_test_expectations_parser.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 
17 namespace gpu {
18 
19 namespace {
20 
21 enum LineParserStage {
22   kLineParserBegin = 0,
23   kLineParserBugID,
24   kLineParserConfigs,
25   kLineParserColon,
26   kLineParserTestName,
27   kLineParserEqual,
28   kLineParserExpectations,
29 };
30 
31 enum Token {
32   // os
33   kConfigWinXP = 0,
34   kConfigWinVista,
35   kConfigWin7,
36   kConfigWin8,
37   kConfigWin10,
38   kConfigWin,
39   kConfigMacLeopard,
40   kConfigMacSnowLeopard,
41   kConfigMacLion,
42   kConfigMacMountainLion,
43   kConfigMacMavericks,
44   kConfigMacYosemite,
45   kConfigMacElCapitan,
46   kConfigMacSierra,
47   kConfigMacHighSierra,
48   kConfigMacMojave,
49   kConfigMacCatalina,
50   kConfigMac,
51   kConfigLinux,
52   kConfigChromeOS,
53   kConfigAndroid,
54   // gpu vendor
55   kConfigNVidia,
56   kConfigAMD,
57   kConfigIntel,
58   kConfigVMWare,
59   // build type
60   kConfigRelease,
61   kConfigDebug,
62   // ANGLE renderer
63   kConfigD3D9,
64   kConfigD3D11,
65   kConfigGLDesktop,
66   kConfigGLES,
67   // expectation
68   kExpectationPass,
69   kExpectationFail,
70   kExpectationFlaky,
71   kExpectationTimeout,
72   kExpectationSkip,
73   // separator
74   kSeparatorColon,
75   kSeparatorEqual,
76 
77   kNumberOfExactMatchTokens,
78 
79   // others
80   kConfigGPUDeviceID,
81   kTokenComment,
82   kTokenWord,
83 };
84 
85 struct TokenInfo {
86   const char* name;
87   int32_t flag;
88 };
89 
90 const TokenInfo kTokenData[] = {
91     {"xp", GPUTestConfig::kOsWinXP},
92     {"vista", GPUTestConfig::kOsWinVista},
93     {"win7", GPUTestConfig::kOsWin7},
94     {"win8", GPUTestConfig::kOsWin8},
95     {"win10", GPUTestConfig::kOsWin10},
96     {"win", GPUTestConfig::kOsWin},
97     {"leopard", GPUTestConfig::kOsMacLeopard},
98     {"snowleopard", GPUTestConfig::kOsMacSnowLeopard},
99     {"lion", GPUTestConfig::kOsMacLion},
100     {"mountainlion", GPUTestConfig::kOsMacMountainLion},
101     {"mavericks", GPUTestConfig::kOsMacMavericks},
102     {"yosemite", GPUTestConfig::kOsMacYosemite},
103     {"elcapitan", GPUTestConfig::kOsMacElCapitan},
104     {"sierra", GPUTestConfig::kOsMacSierra},
105     {"highsierra", GPUTestConfig::kOsMacHighSierra},
106     {"mojave", GPUTestConfig::kOsMacMojave},
107     {"catalina", GPUTestConfig::kOsMacCatalina},
108     {"mac", GPUTestConfig::kOsMac},
109     {"linux", GPUTestConfig::kOsLinux},
110     {"chromeos", GPUTestConfig::kOsChromeOS},
111     {"android", GPUTestConfig::kOsAndroid},
112     {"nvidia", 0x10DE},
113     {"amd", 0x1002},
114     {"intel", 0x8086},
115     {"vmware", 0x15ad},
116     {"release", GPUTestConfig::kBuildTypeRelease},
117     {"debug", GPUTestConfig::kBuildTypeDebug},
118     {"d3d9", GPUTestConfig::kAPID3D9},
119     {"d3d11", GPUTestConfig::kAPID3D11},
120     {"opengl", GPUTestConfig::kAPIGLDesktop},
121     {"gles", GPUTestConfig::kAPIGLES},
122     {"pass", GPUTestExpectationsParser::kGpuTestPass},
123     {"fail", GPUTestExpectationsParser::kGpuTestFail},
124     {"flaky", GPUTestExpectationsParser::kGpuTestFlaky},
125     {"timeout", GPUTestExpectationsParser::kGpuTestTimeout},
126     {"skip", GPUTestExpectationsParser::kGpuTestSkip},
127     {":", 0},
128     {"=", 0},
129 };
130 
131 enum ErrorType {
132   kErrorFileIO = 0,
133   kErrorIllegalEntry,
134   kErrorInvalidEntry,
135   kErrorEntryWithOsConflicts,
136   kErrorEntryWithGpuVendorConflicts,
137   kErrorEntryWithBuildTypeConflicts,
138   kErrorEntryWithAPIConflicts,
139   kErrorEntryWithGpuDeviceIdConflicts,
140   kErrorEntryWithExpectationConflicts,
141   kErrorEntriesOverlap,
142 
143   kNumberOfErrors,
144 };
145 
146 const char* kErrorMessage[] = {
147     "file IO failed",
148     "entry with wrong format",
149     "entry invalid, likely wrong modifiers combination",
150     "entry with OS modifier conflicts",
151     "entry with GPU vendor modifier conflicts",
152     "entry with GPU build type conflicts",
153     "entry with GPU API conflicts",
154     "entry with GPU device id conflicts or malformat",
155     "entry with expectation modifier conflicts",
156     "two entries' configs overlap",
157 };
158 
ParseToken(const std::string & word)159 Token ParseToken(const std::string& word) {
160   if (base::StartsWith(word, "//", base::CompareCase::INSENSITIVE_ASCII))
161     return kTokenComment;
162   if (base::StartsWith(word, "0x", base::CompareCase::INSENSITIVE_ASCII))
163     return kConfigGPUDeviceID;
164 
165   for (int32_t i = 0; i < kNumberOfExactMatchTokens; ++i) {
166     if (base::LowerCaseEqualsASCII(word, kTokenData[i].name))
167       return static_cast<Token>(i);
168   }
169   return kTokenWord;
170 }
171 
172 // reference name can have the last character as *.
NamesMatching(const std::string & ref,const std::string & test_name)173 bool NamesMatching(const std::string& ref, const std::string& test_name) {
174   size_t len = ref.length();
175   if (len == 0)
176     return false;
177   if (ref[len - 1] == '*') {
178     if (test_name.length() > len -1 &&
179         ref.compare(0, len - 1, test_name, 0, len - 1) == 0)
180       return true;
181     return false;
182   }
183   return (ref == test_name);
184 }
185 
186 }  // namespace anonymous
187 
GPUTestExpectationsParser()188 GPUTestExpectationsParser::GPUTestExpectationsParser() {
189   // Some sanity check.
190   DCHECK_EQ(static_cast<unsigned int>(kNumberOfExactMatchTokens),
191             sizeof(kTokenData) / sizeof(kTokenData[0]));
192   DCHECK_EQ(static_cast<unsigned int>(kNumberOfErrors),
193             sizeof(kErrorMessage) / sizeof(kErrorMessage[0]));
194 }
195 
196 GPUTestExpectationsParser::~GPUTestExpectationsParser() = default;
197 
LoadTestExpectations(const std::string & data)198 bool GPUTestExpectationsParser::LoadTestExpectations(const std::string& data) {
199   entries_.clear();
200   error_messages_.clear();
201 
202   std::vector<std::string> lines = base::SplitString(
203       data, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
204   bool rt = true;
205   for (size_t i = 0; i < lines.size(); ++i) {
206     if (!ParseLine(lines[i], i + 1))
207       rt = false;
208   }
209   if (DetectConflictsBetweenEntries()) {
210     entries_.clear();
211     rt = false;
212   }
213 
214   return rt;
215 }
216 
LoadTestExpectations(const base::FilePath & path)217 bool GPUTestExpectationsParser::LoadTestExpectations(
218     const base::FilePath& path) {
219   entries_.clear();
220   error_messages_.clear();
221 
222   std::string data;
223   if (!base::ReadFileToString(path, &data)) {
224     error_messages_.push_back(kErrorMessage[kErrorFileIO]);
225     return false;
226   }
227   return LoadTestExpectations(data);
228 }
229 
GetTestExpectation(const std::string & test_name,const GPUTestBotConfig & bot_config) const230 int32_t GPUTestExpectationsParser::GetTestExpectation(
231     const std::string& test_name,
232     const GPUTestBotConfig& bot_config) const {
233   for (size_t i = 0; i < entries_.size(); ++i) {
234     if (NamesMatching(entries_[i].test_name, test_name) &&
235         bot_config.Matches(entries_[i].test_config))
236       return entries_[i].test_expectation;
237   }
238   return kGpuTestPass;
239 }
240 
241 const std::vector<std::string>&
GetErrorMessages() const242 GPUTestExpectationsParser::GetErrorMessages() const {
243   return error_messages_;
244 }
245 
ParseConfig(const std::string & config_data,GPUTestConfig * config)246 bool GPUTestExpectationsParser::ParseConfig(
247     const std::string& config_data, GPUTestConfig* config) {
248   DCHECK(config);
249   std::vector<std::string> tokens = base::SplitString(
250       config_data, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
251       base::SPLIT_WANT_NONEMPTY);
252 
253   for (size_t i = 0; i < tokens.size(); ++i) {
254     Token token = ParseToken(tokens[i]);
255     switch (token) {
256       case kConfigWinXP:
257       case kConfigWinVista:
258       case kConfigWin7:
259       case kConfigWin8:
260       case kConfigWin10:
261       case kConfigWin:
262       case kConfigMacLeopard:
263       case kConfigMacSnowLeopard:
264       case kConfigMacLion:
265       case kConfigMacMountainLion:
266       case kConfigMacMavericks:
267       case kConfigMacYosemite:
268       case kConfigMacElCapitan:
269       case kConfigMacSierra:
270       case kConfigMacHighSierra:
271       case kConfigMacMojave:
272       case kConfigMacCatalina:
273       case kConfigMac:
274       case kConfigLinux:
275       case kConfigChromeOS:
276       case kConfigAndroid:
277       case kConfigNVidia:
278       case kConfigAMD:
279       case kConfigIntel:
280       case kConfigVMWare:
281       case kConfigRelease:
282       case kConfigDebug:
283       case kConfigD3D9:
284       case kConfigD3D11:
285       case kConfigGLDesktop:
286       case kConfigGLES:
287       case kConfigGPUDeviceID:
288         if (token == kConfigGPUDeviceID) {
289           if (!UpdateTestConfig(config, tokens[i], 0))
290             return false;
291         } else {
292           if (!UpdateTestConfig(config, token, 0))
293             return false;
294         }
295         break;
296       default:
297         return false;
298     }
299   }
300   return true;
301 }
302 
ParseLine(const std::string & line_data,size_t line_number)303 bool GPUTestExpectationsParser::ParseLine(
304     const std::string& line_data, size_t line_number) {
305   std::vector<std::string> tokens = base::SplitString(
306       line_data, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
307       base::SPLIT_WANT_NONEMPTY);
308   int32_t stage = kLineParserBegin;
309   GPUTestExpectationEntry entry;
310   entry.line_number = line_number;
311   GPUTestConfig& config = entry.test_config;
312   bool comments_encountered = false;
313   for (size_t i = 0; i < tokens.size() && !comments_encountered; ++i) {
314     Token token = ParseToken(tokens[i]);
315     switch (token) {
316       case kTokenComment:
317         comments_encountered = true;
318         break;
319       case kConfigWinXP:
320       case kConfigWinVista:
321       case kConfigWin7:
322       case kConfigWin8:
323       case kConfigWin10:
324       case kConfigWin:
325       case kConfigMacLeopard:
326       case kConfigMacSnowLeopard:
327       case kConfigMacLion:
328       case kConfigMacMountainLion:
329       case kConfigMacMavericks:
330       case kConfigMacYosemite:
331       case kConfigMacElCapitan:
332       case kConfigMacSierra:
333       case kConfigMacHighSierra:
334       case kConfigMacMojave:
335       case kConfigMacCatalina:
336       case kConfigMac:
337       case kConfigLinux:
338       case kConfigChromeOS:
339       case kConfigAndroid:
340       case kConfigNVidia:
341       case kConfigAMD:
342       case kConfigIntel:
343       case kConfigVMWare:
344       case kConfigRelease:
345       case kConfigDebug:
346       case kConfigD3D9:
347       case kConfigD3D11:
348       case kConfigGLDesktop:
349       case kConfigGLES:
350       case kConfigGPUDeviceID:
351         // MODIFIERS, could be in any order, need at least one.
352         if (stage != kLineParserConfigs && stage != kLineParserBugID) {
353           PushErrorMessage(kErrorMessage[kErrorIllegalEntry],
354                            line_number);
355           return false;
356         }
357         if (token == kConfigGPUDeviceID) {
358           if (!UpdateTestConfig(&config, tokens[i], line_number))
359             return false;
360         } else {
361           if (!UpdateTestConfig(&config, token, line_number))
362             return false;
363         }
364         if (stage == kLineParserBugID)
365           stage++;
366         break;
367       case kSeparatorColon:
368         // :
369         if (stage != kLineParserConfigs) {
370           PushErrorMessage(kErrorMessage[kErrorIllegalEntry],
371                            line_number);
372           return false;
373         }
374         stage++;
375         break;
376       case kSeparatorEqual:
377         // =
378         if (stage != kLineParserTestName) {
379           PushErrorMessage(kErrorMessage[kErrorIllegalEntry],
380                            line_number);
381           return false;
382         }
383         stage++;
384         break;
385       case kTokenWord:
386         // BUG_ID or TEST_NAME
387         if (stage == kLineParserBegin) {
388           // Bug ID is not used for anything; ignore it.
389         } else if (stage == kLineParserColon) {
390           entry.test_name = tokens[i];
391         } else {
392           PushErrorMessage(kErrorMessage[kErrorIllegalEntry],
393                            line_number);
394           return false;
395         }
396         stage++;
397         break;
398       case kExpectationPass:
399       case kExpectationFail:
400       case kExpectationFlaky:
401       case kExpectationTimeout:
402       case kExpectationSkip:
403         // TEST_EXPECTATIONS
404         if (stage != kLineParserEqual && stage != kLineParserExpectations) {
405           PushErrorMessage(kErrorMessage[kErrorIllegalEntry],
406                            line_number);
407           return false;
408         }
409         if ((kTokenData[token].flag & entry.test_expectation) != 0) {
410           PushErrorMessage(kErrorMessage[kErrorEntryWithExpectationConflicts],
411                            line_number);
412           return false;
413         }
414         entry.test_expectation =
415             (kTokenData[token].flag | entry.test_expectation);
416         if (stage == kLineParserEqual)
417           stage++;
418         break;
419       default:
420         DCHECK(false);
421         break;
422     }
423   }
424   if (stage == kLineParserBegin) {
425     // The whole line is empty or all comments
426     return true;
427   }
428   if (stage == kLineParserExpectations) {
429     if (!config.IsValid()) {
430         PushErrorMessage(kErrorMessage[kErrorInvalidEntry], line_number);
431         return false;
432     }
433     entries_.push_back(entry);
434     return true;
435   }
436   PushErrorMessage(kErrorMessage[kErrorIllegalEntry], line_number);
437   return false;
438 }
439 
UpdateTestConfig(GPUTestConfig * config,int32_t token,size_t line_number)440 bool GPUTestExpectationsParser::UpdateTestConfig(GPUTestConfig* config,
441                                                  int32_t token,
442                                                  size_t line_number) {
443   DCHECK(config);
444   switch (token) {
445     case kConfigWinXP:
446     case kConfigWinVista:
447     case kConfigWin7:
448     case kConfigWin8:
449     case kConfigWin10:
450     case kConfigWin:
451     case kConfigMacLeopard:
452     case kConfigMacSnowLeopard:
453     case kConfigMacLion:
454     case kConfigMacMountainLion:
455     case kConfigMacMavericks:
456     case kConfigMacYosemite:
457     case kConfigMacElCapitan:
458     case kConfigMacSierra:
459     case kConfigMacHighSierra:
460     case kConfigMacMojave:
461     case kConfigMacCatalina:
462     case kConfigMac:
463     case kConfigLinux:
464     case kConfigChromeOS:
465     case kConfigAndroid:
466       if ((config->os() & kTokenData[token].flag) != 0) {
467         PushErrorMessage(kErrorMessage[kErrorEntryWithOsConflicts],
468                          line_number);
469         return false;
470       }
471       config->set_os(config->os() | kTokenData[token].flag);
472       break;
473     case kConfigNVidia:
474     case kConfigAMD:
475     case kConfigIntel:
476     case kConfigVMWare:
477       {
478       uint32_t gpu_vendor = static_cast<uint32_t>(kTokenData[token].flag);
479         for (size_t i = 0; i < config->gpu_vendor().size(); ++i) {
480           if (config->gpu_vendor()[i] == gpu_vendor) {
481             PushErrorMessage(
482                 kErrorMessage[kErrorEntryWithGpuVendorConflicts],
483                 line_number);
484             return false;
485           }
486         }
487         config->AddGPUVendor(gpu_vendor);
488       }
489       break;
490     case kConfigRelease:
491     case kConfigDebug:
492       if ((config->build_type() & kTokenData[token].flag) != 0) {
493         PushErrorMessage(
494             kErrorMessage[kErrorEntryWithBuildTypeConflicts],
495             line_number);
496         return false;
497       }
498       config->set_build_type(
499           config->build_type() | kTokenData[token].flag);
500       break;
501     case kConfigD3D9:
502     case kConfigD3D11:
503     case kConfigGLDesktop:
504     case kConfigGLES:
505       if ((config->api() & kTokenData[token].flag) != 0) {
506         PushErrorMessage(kErrorMessage[kErrorEntryWithAPIConflicts],
507                          line_number);
508         return false;
509       }
510       config->set_api(config->api() | kTokenData[token].flag);
511       break;
512     default:
513       DCHECK(false);
514       break;
515   }
516   return true;
517 }
518 
UpdateTestConfig(GPUTestConfig * config,const std::string & gpu_device_id,size_t line_number)519 bool GPUTestExpectationsParser::UpdateTestConfig(
520     GPUTestConfig* config,
521     const std::string& gpu_device_id,
522     size_t line_number) {
523   DCHECK(config);
524   uint32_t device_id = 0;
525   if (config->gpu_device_id() != 0 ||
526       !base::HexStringToUInt(gpu_device_id, &device_id) ||
527       device_id == 0) {
528     PushErrorMessage(kErrorMessage[kErrorEntryWithGpuDeviceIdConflicts],
529                      line_number);
530     return false;
531   }
532   config->set_gpu_device_id(device_id);
533   return true;
534 }
535 
DetectConflictsBetweenEntries()536 bool GPUTestExpectationsParser::DetectConflictsBetweenEntries() {
537   bool rt = false;
538   for (size_t i = 0; i < entries_.size(); ++i) {
539     for (size_t j = i + 1; j < entries_.size(); ++j) {
540       if (entries_[i].test_name == entries_[j].test_name &&
541           entries_[i].test_config.OverlapsWith(entries_[j].test_config)) {
542         PushErrorMessage(kErrorMessage[kErrorEntriesOverlap],
543                          entries_[i].line_number,
544                          entries_[j].line_number);
545         rt = true;
546       }
547     }
548   }
549   return rt;
550 }
551 
PushErrorMessage(const std::string & message,size_t line_number)552 void GPUTestExpectationsParser::PushErrorMessage(
553     const std::string& message, size_t line_number) {
554   error_messages_.push_back(
555       base::StringPrintf("Line %d : %s",
556                          static_cast<int>(line_number), message.c_str()));
557 }
558 
PushErrorMessage(const std::string & message,size_t entry1_line_number,size_t entry2_line_number)559 void GPUTestExpectationsParser::PushErrorMessage(
560     const std::string& message,
561     size_t entry1_line_number,
562     size_t entry2_line_number) {
563   error_messages_.push_back(
564       base::StringPrintf("Line %d and %d : %s",
565                          static_cast<int>(entry1_line_number),
566                          static_cast<int>(entry2_line_number),
567                          message.c_str()));
568 }
569 
GPUTestExpectationEntry()570 GPUTestExpectationsParser:: GPUTestExpectationEntry::GPUTestExpectationEntry()
571     : test_expectation(0),
572       line_number(0) {
573 }
574 
575 }  // namespace gpu
576