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