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
14namespace gpu {
15
16namespace {
17
18enum LineParserStage {
19  kLineParserBegin = 0,
20  kLineParserBugID,
21  kLineParserConfigs,
22  kLineParserColon,
23  kLineParserTestName,
24  kLineParserEqual,
25  kLineParserExpectations,
26};
27
28enum 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
70struct TokenInfo {
71  const char* name;
72  int32 flag;
73};
74
75const 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
105enum 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
119const 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
131Token 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 *.
145bool 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
160GPUTestExpectationsParser::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
168GPUTestExpectationsParser::~GPUTestExpectationsParser() {
169}
170
171bool 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
190bool 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
203int32 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
214const std::vector<std::string>&
215GPUTestExpectationsParser::GetErrorMessages() const {
216  return error_messages_;
217}
218
219bool 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
264bool 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
389bool 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
450bool 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
467bool 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
483void 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
490void 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
501GPUTestExpectationsParser:: GPUTestExpectationEntry::GPUTestExpectationEntry()
502    : test_expectation(0),
503      line_number(0) {
504}
505
506}  // namespace gpu
507
508