diff options
Diffstat (limited to 'bestflags')
-rw-r--r-- | bestflags/flags.py | 154 | ||||
-rw-r--r-- | bestflags/flags_test.py | 192 |
2 files changed, 346 insertions, 0 deletions
diff --git a/bestflags/flags.py b/bestflags/flags.py new file mode 100644 index 00000000..b1f337ab --- /dev/null +++ b/bestflags/flags.py @@ -0,0 +1,154 @@ +# Copyright (c) 2013 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. + +"""Manage bundles of flags used for the optimizing of ChromeOS. + +Part of the Chrome build flags optimization. + +The content of this module is adapted from the Trakhelp JVM project. This module +contains the basic Class Flag and the Class FlagSet. The core abstractions are: + +The class Flag, which takes a domain specific language describing how to fill +the flags with values. + +The class FlagSet, which contains a number of flags and can create new FlagSets +by mixing with other FlagSets. + +The Flag DSL works by replacing value ranges in [x-y] with numbers in the range +x through y. + +Examples: + "foo[0-9]bar" will expand to e.g. "foo5bar". +""" + +__author__ = 'yuhenglong@google.com (Yuheng Long)' + +import random +import re + +# +# This matches a [...] group in the internal representation for a flag +# specification, and is used in "filling out" flags - placing values inside +# the flag_spec. The internal flag_spec format is like "foo[0]", with +# values filled out like 5; this would be transformed by +# FormattedForUse() into "foo5". +_FLAG_FILLOUT_VALUE_RE = re.compile(r'\[([^\]]*)\]') + +# This matches a numeric flag flag=[start-end]. +rx = re.compile(r'\[(?P<start>\d+)-(?P<end>\d+)\]') + + +class Flag(object): + """A class representing a particular command line flag argument. + + The Flag consists of two parts: The spec and the value. + The spec is a definition in the little language, a string of the form + [<start>-<end>] where start and end is an positive integer for a fillable + value. + + An example of a spec is "foo[0-9]". + """ + + def __init__(self, spec, value=-1): + self._spec = spec + + # If the value is not specified, a random value is used. + if value == -1: + result = rx.search(spec) + + # If this is a numeric flag, a value is chosen within start and end, start + # inclusive and end exclusive. + if result: + start = int(result.group('start')) + end = int(result.group('end')) + + assert start < end + value = random.randint(start, end) + + self._value = value + + def __eq__(self, other): + if isinstance(other, Flag): + return self._spec == other.GetSpec() and self._value == other.GetValue() + return False + + def __hash__(self): + return hash(self._spec) + self._value + + def GetValue(self): + """Get the value for this flag. + + Returns: + The value. + """ + + return self._value + + def GetSpec(self): + """Get the spec for this flag. + + Returns: + The spec. + """ + + return self._spec + + def FormattedForUse(self): + """Calculate the combination of flag_spec and values. + + For e.g. the flag_spec 'foo[0-9]' and the value equals to 5, this will + return 'foo5'. The filled out version of the flag is the text string you use + when you actually want to pass the flag to some binary. + + Returns: + A string that represent the filled out flag, e.g. the flag with the + FlagSpec '-X[0-9]Y' and value equals to 5 would return '-X5Y'. + """ + + return _FLAG_FILLOUT_VALUE_RE.sub(str(self._value), self._spec) + + +class FlagSet(object): + """A dictionary of Flag objects. + + The flags dictionary stores the spec and flag pair. + """ + + def __init__(self, flag_array): + # Store the flags as a dictionary mapping of spec -> flag object + self._flags = dict([(flag.GetSpec(), flag) for flag in flag_array]) + + def __eq__(self, other): + return isinstance(other, FlagSet) and self._flags == other.GetFlags() + + def __hash__(self): + return sum([hash(flag) for flag in self._flags.values()]) + + def __getitem__(self, flag_spec): + """Get flag with a particular flag_spec. + + Args: + flag_spec: The flag_spec to find. + + Returns: + A flag. + """ + + return self._flags[flag_spec] + + def __contains__(self, flag_spec): + return self._flags.has_key(flag_spec) + + def GetFlags(self): + return self._flags + + def FormattedForUse(self): + """Format this for use in an application. + + Returns: + A list of flags, sorted alphabetically and filled in with the values + for each flag. + """ + + return sorted([f.FormattedForUse() for f in self._flags.values()]) diff --git a/bestflags/flags_test.py b/bestflags/flags_test.py new file mode 100644 index 00000000..3c424ff6 --- /dev/null +++ b/bestflags/flags_test.py @@ -0,0 +1,192 @@ +# Copyright (c) 2013 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. + +"""Flag unittest. + +Part of the Chrome build flags optimization. +""" + +__author__ = 'yuhenglong@google.com (Yuheng Long)' + +import random +import sys +import unittest + +from flags import Flag +from flags import FlagSet + +# The number of tests to test. +NUM_TESTS = 20 + + +class FlagTest(unittest.TestCase): + """This class tests the Flag class.""" + + def testInit(self): + """The value generated should fall within start and end of the spec. + + If the value is not specified, the value generated should fall within start + and end of the spec. + """ + + for _ in range(NUM_TESTS): + start = random.randint(1, sys.maxint - 1) + end = random.randint(start + 1, sys.maxint) + + spec = 'flag=[%s-%s]' % (start, end) + + test_flag = Flag(spec) + + value = test_flag.GetValue() + + # If the value is not specified when the flag is constructed, a random + # value is chosen. This value should fall within start and end of the + # spec. + assert start <= value and value < end + + def testEqual(self): + """Test the equal method of the flag. + + Two flags are equal if and only if their spec and value are equal. + """ + + tests = range(NUM_TESTS) + + # Two tasks having the same spec and value should be equivalent. + for test in tests: + assert Flag(str(test), test) == Flag(str(test), test) + + # Two tasks having different flag set should be different. + for test in tests: + flag0 = Flag(str(test), test) + other_flag_sets = [other for other in tests if test != other] + for test1 in other_flag_sets: + flag1 = Flag(str(test1), test1) + assert flag0 != flag1 + + def testFormattedForUse(self): + """Test the FormattedForUse method of the flag. + + The FormattedForUse replaces the string within the [] with the actual value. + """ + + for _ in range(NUM_TESTS): + start = random.randint(1, sys.maxint - 1) + end = random.randint(start + 1, sys.maxint) + value = random.randint(start, end - 1) + + spec = 'flag=[%s-%s]' % (start, end) + + test_flag = Flag(spec, value) + + # For numeric flag, the FormattedForUse replaces the string within the [] + # with the actual value. + test_value = test_flag.FormattedForUse() + actual_value = 'flag=%s' % value + + assert test_value == actual_value + + for _ in range(NUM_TESTS): + value = random.randint(1, sys.maxint - 1) + + test_flag = Flag('flag', value) + + # For boolean flag, the FormattedForUse returns the spec. + test_value = test_flag.FormattedForUse() + actual_value = 'flag' + assert test_value == actual_value + + +class FlagSetTest(unittest.TestCase): + """This class test the FlagSet class.""" + + def testEqual(self): + """Test the equal method of the Class FlagSet. + + Two FlagSet instances are equal if all their flags are equal. + """ + + flag_names = range(NUM_TESTS) + + # Two flag sets having the same flags should be equivalent. + for flag_name in flag_names: + spec = '%s' % flag_name + + assert FlagSet([Flag(spec)]) == FlagSet([Flag(spec)]) + + # Two flag sets having different flags should be different. + for flag_name in flag_names: + spec = '%s' % flag_name + flag_set = FlagSet([Flag(spec)]) + other_flag_sets = [other for other in flag_names if flag_name != other] + for other_name in other_flag_sets: + other_spec = '%s' % other_name + assert flag_set != FlagSet([Flag(other_spec)]) + + def testGetItem(self): + """Test the get item method of the Class FlagSet. + + The flag set is also indexed by the specs. The flag set should return the + appropriate flag given the spec. + """ + + tests = range(NUM_TESTS) + + specs = [str(spec) for spec in tests] + flag_array = [Flag(spec) for spec in specs] + + flag_set = FlagSet(flag_array) + + # Created a dictionary of spec and flag, the flag set should return the flag + # the same as this dictionary. + spec_flag = dict(zip(specs, flag_array)) + + for spec in spec_flag: + assert flag_set[spec] == spec_flag[spec] + + def testContain(self): + """Test the contain method of the Class FlagSet. + + The flag set is also indexed by the specs. The flag set should return true + for spec if it contains a flag containing spec. + """ + + true_tests = range(NUM_TESTS) + false_tests = range(NUM_TESTS, NUM_TESTS + NUM_TESTS) + + specs = [str(spec) for spec in true_tests] + + flag_set = FlagSet([Flag(spec) for spec in specs]) + + for spec in specs: + assert spec in flag_set + + for spec in false_tests: + assert spec not in flag_set + + def testFormattedForUse(self): + """Test the FormattedForUse method of the Class FlagSet. + + The output should be a sorted list of strings. + """ + + flag_names = range(NUM_TESTS) + flag_names.reverse() + flags = [] + result = [] + + # Construct the flag set. + for flag_name in flag_names: + spec = '%s' % flag_name + flags.append(Flag(spec)) + result.append(spec) + + flag_set = FlagSet(flags) + + # The results string should be sorted. + assert sorted(result) == flag_set.FormattedForUse() + + +if __name__ == '__main__': + unittest.main() |