aboutsummaryrefslogtreecommitdiff
path: root/bestflags
diff options
context:
space:
mode:
authorYuheng Long <yuhenglong@google.com>2013-07-29 15:39:30 -0700
committerChromeBot <chrome-bot@google.com>2013-08-02 18:22:53 -0700
commitd67dcb71687152d17dff6b84a395f87e243f8875 (patch)
tree09be9111279d293954029ba8eb75a1af6ba71200 /bestflags
parentffd98bbabbe8f7287d79e7ca111418e794fb5880 (diff)
downloadtoolchain-utils-d67dcb71687152d17dff6b84a395f87e243f8875.tar.gz
Add the flag module.
BUG=None TEST=unit testings for the pipeline stage, pipeline workers, generation, steering, task and flag. Change-Id: Ia1b32f859da2fc0984b940393b688a44221f3611 Reviewed-on: https://gerrit-int.chromium.org/41847 Reviewed-by: Simon Que <sque@google.com> Reviewed-by: Luis Lozano <llozano@chromium.org> Commit-Queue: Yuheng Long <yuhenglong@google.com> Tested-by: Yuheng Long <yuhenglong@google.com>
Diffstat (limited to 'bestflags')
-rw-r--r--bestflags/flags.py154
-rw-r--r--bestflags/flags_test.py192
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()