• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2011 The Chromium OS 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"""The experiment file module. It manages the input file of crosperf."""
5
6from __future__ import print_function
7import os.path
8import re
9from settings_factory import SettingsFactory
10
11
12class ExperimentFile(object):
13  """Class for parsing the experiment file format.
14
15  The grammar for this format is:
16
17  experiment = { _FIELD_VALUE_RE | settings }
18  settings = _OPEN_SETTINGS_RE
19             { _FIELD_VALUE_RE }
20             _CLOSE_SETTINGS_RE
21
22  Where the regexes are terminals defined below. This results in an format
23  which looks something like:
24
25  field_name: value
26  settings_type: settings_name {
27    field_name: value
28    field_name: value
29  }
30  """
31
32  # Field regex, e.g. "iterations: 3"
33  _FIELD_VALUE_RE = re.compile(r'(\+)?\s*(\w+?)(?:\.(\S+))?\s*:\s*(.*)')
34  # Open settings regex, e.g. "label {"
35  _OPEN_SETTINGS_RE = re.compile(r'(?:([\w.-]+):)?\s*([\w.-]+)\s*{')
36  # Close settings regex.
37  _CLOSE_SETTINGS_RE = re.compile(r'}')
38
39  def __init__(self, experiment_file, overrides=None):
40    """Construct object from file-like experiment_file.
41
42    Args:
43      experiment_file: file-like object with text description of experiment.
44      overrides: A settings object that will override fields in other settings.
45
46    Raises:
47      Exception: if invalid build type or description is invalid.
48    """
49    self.all_settings = []
50    self.global_settings = SettingsFactory().GetSettings('global', 'global')
51    self.all_settings.append(self.global_settings)
52
53    self._Parse(experiment_file)
54
55    for settings in self.all_settings:
56      settings.Inherit()
57      settings.Validate()
58      if overrides:
59        settings.Override(overrides)
60
61  def GetSettings(self, settings_type):
62    """Return nested fields from the experiment file."""
63    res = []
64    for settings in self.all_settings:
65      if settings.settings_type == settings_type:
66        res.append(settings)
67    return res
68
69  def GetGlobalSettings(self):
70    """Return the global fields from the experiment file."""
71    return self.global_settings
72
73  def _ParseField(self, reader):
74    """Parse a key/value field."""
75    line = reader.CurrentLine().strip()
76    match = ExperimentFile._FIELD_VALUE_RE.match(line)
77    append, name, _, text_value = match.groups()
78    return (name, text_value, append)
79
80  def _ParseSettings(self, reader):
81    """Parse a settings block."""
82    line = reader.CurrentLine().strip()
83    match = ExperimentFile._OPEN_SETTINGS_RE.match(line)
84    settings_type = match.group(1)
85    if settings_type is None:
86      settings_type = ''
87    settings_name = match.group(2)
88    settings = SettingsFactory().GetSettings(settings_name, settings_type)
89    settings.SetParentSettings(self.global_settings)
90
91    while reader.NextLine():
92      line = reader.CurrentLine().strip()
93
94      if not line:
95        continue
96      elif ExperimentFile._FIELD_VALUE_RE.match(line):
97        field = self._ParseField(reader)
98        settings.SetField(field[0], field[1], field[2])
99      elif ExperimentFile._CLOSE_SETTINGS_RE.match(line):
100        return settings
101
102    raise EOFError('Unexpected EOF while parsing settings block.')
103
104  def _Parse(self, experiment_file):
105    """Parse experiment file and create settings."""
106    reader = ExperimentFileReader(experiment_file)
107    settings_names = {}
108    try:
109      while reader.NextLine():
110        line = reader.CurrentLine().strip()
111
112        if not line:
113          continue
114        elif ExperimentFile._OPEN_SETTINGS_RE.match(line):
115          new_settings = self._ParseSettings(reader)
116          if new_settings.name in settings_names:
117            raise SyntaxError(
118                "Duplicate settings name: '%s'." % new_settings.name)
119          settings_names[new_settings.name] = True
120          self.all_settings.append(new_settings)
121        elif ExperimentFile._FIELD_VALUE_RE.match(line):
122          field = self._ParseField(reader)
123          self.global_settings.SetField(field[0], field[1], field[2])
124        else:
125          raise IOError('Unexpected line.')
126    except Exception, err:
127      raise RuntimeError('Line %d: %s\n==> %s' % (reader.LineNo(), str(err),
128                                                  reader.CurrentLine(False)))
129
130  def Canonicalize(self):
131    """Convert parsed experiment file back into an experiment file."""
132    res = ''
133    board = ''
134    for field_name in self.global_settings.fields:
135      field = self.global_settings.fields[field_name]
136      if field.assigned:
137        res += '%s: %s\n' % (field.name, field.GetString())
138      if field.name == 'board':
139        board = field.GetString()
140    res += '\n'
141
142    for settings in self.all_settings:
143      if settings.settings_type != 'global':
144        res += '%s: %s {\n' % (settings.settings_type, settings.name)
145        for field_name in settings.fields:
146          field = settings.fields[field_name]
147          if field.assigned:
148            res += '\t%s: %s\n' % (field.name, field.GetString())
149            if field.name == 'chromeos_image':
150              real_file = (
151                  os.path.realpath(os.path.expanduser(field.GetString())))
152              if real_file != field.GetString():
153                res += '\t#actual_image: %s\n' % real_file
154            if field.name == 'build':
155              chromeos_root_field = settings.fields['chromeos_root']
156              if chromeos_root_field:
157                chromeos_root = chromeos_root_field.GetString()
158              value = field.GetString()
159              autotest_field = settings.fields['autotest_path']
160              autotest_path = ''
161              if autotest_field.assigned:
162                autotest_path = autotest_field.GetString()
163              image_path, autotest_path = settings.GetXbuddyPath(
164                  value, autotest_path, board, chromeos_root, 'quiet')
165              res += '\t#actual_image: %s\n' % image_path
166              if not autotest_field.assigned:
167                res += '\t#actual_autotest_path: %s\n' % autotest_path
168
169        res += '}\n\n'
170
171    return res
172
173
174class ExperimentFileReader(object):
175  """Handle reading lines from an experiment file."""
176
177  def __init__(self, file_object):
178    self.file_object = file_object
179    self.current_line = None
180    self.current_line_no = 0
181
182  def CurrentLine(self, strip_comment=True):
183    """Return the next line from the file, without advancing the iterator."""
184    if strip_comment:
185      return self._StripComment(self.current_line)
186    return self.current_line
187
188  def NextLine(self, strip_comment=True):
189    """Advance the iterator and return the next line of the file."""
190    self.current_line_no += 1
191    self.current_line = self.file_object.readline()
192    return self.CurrentLine(strip_comment)
193
194  def _StripComment(self, line):
195    """Strip comments starting with # from a line."""
196    if '#' in line:
197      line = line[:line.find('#')] + line[-1]
198    return line
199
200  def LineNo(self):
201    """Return the current line number."""
202    return self.current_line_no
203