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