You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
7.2 KiB
205 lines
7.2 KiB
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
|
# Use of this source code is governed by a BSD-style license that can be |
|
# found in the LICENSE file. |
|
"""The experiment file module. It manages the input file of crosperf.""" |
|
|
|
from __future__ import print_function |
|
import os.path |
|
import re |
|
from settings_factory import SettingsFactory |
|
|
|
|
|
class ExperimentFile(object): |
|
"""Class for parsing the experiment file format. |
|
|
|
The grammar for this format is: |
|
|
|
experiment = { _FIELD_VALUE_RE | settings } |
|
settings = _OPEN_SETTINGS_RE |
|
{ _FIELD_VALUE_RE } |
|
_CLOSE_SETTINGS_RE |
|
|
|
Where the regexes are terminals defined below. This results in an format |
|
which looks something like: |
|
|
|
field_name: value |
|
settings_type: settings_name { |
|
field_name: value |
|
field_name: value |
|
} |
|
""" |
|
|
|
# Field regex, e.g. "iterations: 3" |
|
_FIELD_VALUE_RE = re.compile(r'(\+)?\s*(\w+?)(?:\.(\S+))?\s*:\s*(.*)') |
|
# Open settings regex, e.g. "label {" |
|
_OPEN_SETTINGS_RE = re.compile(r'(?:([\w.-]+):)?\s*([\w.-]+)\s*{') |
|
# Close settings regex. |
|
_CLOSE_SETTINGS_RE = re.compile(r'}') |
|
|
|
def __init__(self, experiment_file, overrides=None): |
|
"""Construct object from file-like experiment_file. |
|
|
|
Args: |
|
experiment_file: file-like object with text description of experiment. |
|
overrides: A settings object that will override fields in other settings. |
|
|
|
Raises: |
|
Exception: if invalid build type or description is invalid. |
|
""" |
|
self.all_settings = [] |
|
self.global_settings = SettingsFactory().GetSettings('global', 'global') |
|
self.all_settings.append(self.global_settings) |
|
|
|
self._Parse(experiment_file) |
|
|
|
for settings in self.all_settings: |
|
settings.Inherit() |
|
settings.Validate() |
|
if overrides: |
|
settings.Override(overrides) |
|
|
|
def GetSettings(self, settings_type): |
|
"""Return nested fields from the experiment file.""" |
|
res = [] |
|
for settings in self.all_settings: |
|
if settings.settings_type == settings_type: |
|
res.append(settings) |
|
return res |
|
|
|
def GetGlobalSettings(self): |
|
"""Return the global fields from the experiment file.""" |
|
return self.global_settings |
|
|
|
def _ParseField(self, reader): |
|
"""Parse a key/value field.""" |
|
line = reader.CurrentLine().strip() |
|
match = ExperimentFile._FIELD_VALUE_RE.match(line) |
|
append, name, _, text_value = match.groups() |
|
return (name, text_value, append) |
|
|
|
def _ParseSettings(self, reader): |
|
"""Parse a settings block.""" |
|
line = reader.CurrentLine().strip() |
|
match = ExperimentFile._OPEN_SETTINGS_RE.match(line) |
|
settings_type = match.group(1) |
|
if settings_type is None: |
|
settings_type = '' |
|
settings_name = match.group(2) |
|
settings = SettingsFactory().GetSettings(settings_name, settings_type) |
|
settings.SetParentSettings(self.global_settings) |
|
|
|
while reader.NextLine(): |
|
line = reader.CurrentLine().strip() |
|
|
|
if not line: |
|
continue |
|
elif ExperimentFile._FIELD_VALUE_RE.match(line): |
|
field = self._ParseField(reader) |
|
settings.SetField(field[0], field[1], field[2]) |
|
elif ExperimentFile._CLOSE_SETTINGS_RE.match(line): |
|
return settings |
|
|
|
raise EOFError('Unexpected EOF while parsing settings block.') |
|
|
|
def _Parse(self, experiment_file): |
|
"""Parse experiment file and create settings.""" |
|
reader = ExperimentFileReader(experiment_file) |
|
settings_names = {} |
|
try: |
|
while reader.NextLine(): |
|
line = reader.CurrentLine().strip() |
|
|
|
if not line: |
|
continue |
|
elif ExperimentFile._OPEN_SETTINGS_RE.match(line): |
|
new_settings = self._ParseSettings(reader) |
|
if new_settings.name in settings_names: |
|
raise SyntaxError("Duplicate settings name: '%s'." % |
|
new_settings.name) |
|
settings_names[new_settings.name] = True |
|
self.all_settings.append(new_settings) |
|
elif ExperimentFile._FIELD_VALUE_RE.match(line): |
|
field = self._ParseField(reader) |
|
self.global_settings.SetField(field[0], field[1], field[2]) |
|
else: |
|
raise IOError('Unexpected line.') |
|
except Exception, err: |
|
raise RuntimeError('Line %d: %s\n==> %s' % (reader.LineNo(), str(err), |
|
reader.CurrentLine(False))) |
|
|
|
def Canonicalize(self): |
|
"""Convert parsed experiment file back into an experiment file.""" |
|
res = '' |
|
board = '' |
|
for field_name in self.global_settings.fields: |
|
field = self.global_settings.fields[field_name] |
|
if field.assigned: |
|
res += '%s: %s\n' % (field.name, field.GetString()) |
|
if field.name == 'board': |
|
board = field.GetString() |
|
res += '\n' |
|
|
|
for settings in self.all_settings: |
|
if settings.settings_type != 'global': |
|
res += '%s: %s {\n' % (settings.settings_type, settings.name) |
|
for field_name in settings.fields: |
|
field = settings.fields[field_name] |
|
if field.assigned: |
|
res += '\t%s: %s\n' % (field.name, field.GetString()) |
|
if field.name == 'chromeos_image': |
|
real_file = ( |
|
os.path.realpath(os.path.expanduser(field.GetString()))) |
|
if real_file != field.GetString(): |
|
res += '\t#actual_image: %s\n' % real_file |
|
if field.name == 'build': |
|
chromeos_root_field = settings.fields['chromeos_root'] |
|
if chromeos_root_field: |
|
chromeos_root = chromeos_root_field.GetString() |
|
value = field.GetString() |
|
autotest_field = settings.fields['autotest_path'] |
|
autotest_path = '' |
|
if autotest_field.assigned: |
|
autotest_path = autotest_field.GetString() |
|
image_path, autotest_path = settings.GetXbuddyPath(value, |
|
autotest_path, |
|
board, |
|
chromeos_root, |
|
'quiet') |
|
res += '\t#actual_image: %s\n' % image_path |
|
if not autotest_field.assigned: |
|
res += '\t#actual_autotest_path: %s\n' % autotest_path |
|
|
|
res += '}\n\n' |
|
|
|
return res |
|
|
|
|
|
class ExperimentFileReader(object): |
|
"""Handle reading lines from an experiment file.""" |
|
|
|
def __init__(self, file_object): |
|
self.file_object = file_object |
|
self.current_line = None |
|
self.current_line_no = 0 |
|
|
|
def CurrentLine(self, strip_comment=True): |
|
"""Return the next line from the file, without advancing the iterator.""" |
|
if strip_comment: |
|
return self._StripComment(self.current_line) |
|
return self.current_line |
|
|
|
def NextLine(self, strip_comment=True): |
|
"""Advance the iterator and return the next line of the file.""" |
|
self.current_line_no += 1 |
|
self.current_line = self.file_object.readline() |
|
return self.CurrentLine(strip_comment) |
|
|
|
def _StripComment(self, line): |
|
"""Strip comments starting with # from a line.""" |
|
if '#' in line: |
|
line = line[:line.find('#')] + line[-1] |
|
return line |
|
|
|
def LineNo(self): |
|
"""Return the current line number.""" |
|
return self.current_line_no
|
|
|