Commit 76c6ea28 authored by Paweł Hajdan, Jr's avatar Paweł Hajdan, Jr Committed by Commit Bot

gclient_eval: implement boolean expressions

Bug: 570091
Change-Id: I72326ee6ddd907a97b5c497a8b2cc7fb744cf422
Reviewed-on: https://chromium-review.googlesource.com/523142Reviewed-by: 's avatarDirk Pranke <dpranke@chromium.org>
Commit-Queue: Paweł Hajdan Jr. <phajdan.jr@chromium.org>
parent be812619
......@@ -196,3 +196,69 @@ def Exec(content, global_scope, local_scope, filename='<unknown>'):
getattr(node_or_string, 'lineno', '<unknown>')))
_GCLIENT_SCHEMA.validate(local_scope)
def EvaluateCondition(condition, variables, referenced_variables=None):
"""Safely evaluates a boolean condition. Returns the result."""
if not referenced_variables:
referenced_variables = set()
_allowed_names = {'None': None, 'True': True, 'False': False}
main_node = ast.parse(condition, mode='eval')
if isinstance(main_node, ast.Expression):
main_node = main_node.body
def _convert(node):
if isinstance(node, ast.Str):
return node.s
elif isinstance(node, ast.Name):
if node.id in referenced_variables:
raise ValueError(
'invalid cyclic reference to %r (inside %r)' % (
node.id, condition))
elif node.id in _allowed_names:
return _allowed_names[node.id]
elif node.id in variables:
return EvaluateCondition(
variables[node.id],
variables,
referenced_variables.union([node.id]))
else:
raise ValueError(
'invalid name %r (inside %r)' % (node.id, condition))
elif isinstance(node, ast.BoolOp) and isinstance(node.op, ast.Or):
if len(node.values) != 2:
raise ValueError(
'invalid "or": exactly 2 operands required (inside %r)' % (
condition))
return _convert(node.values[0]) or _convert(node.values[1])
elif isinstance(node, ast.BoolOp) and isinstance(node.op, ast.And):
if len(node.values) != 2:
raise ValueError(
'invalid "and": exactly 2 operands required (inside %r)' % (
condition))
return _convert(node.values[0]) and _convert(node.values[1])
elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
return not _convert(node.operand)
elif isinstance(node, ast.Compare):
if len(node.ops) != 1:
raise ValueError(
'invalid compare: exactly 1 operator required (inside %r)' % (
condition))
if len(node.comparators) != 1:
raise ValueError(
'invalid compare: exactly 1 comparator required (inside %r)' % (
condition))
left = _convert(node.left)
right = _convert(node.comparators[0])
if isinstance(node.ops[0], ast.Eq):
return left == right
raise ValueError(
'unexpected operator: %s %s (inside %r)' % (
node.ops[0], ast.dump(node), condition))
else:
raise ValueError(
'unexpected AST node: %s %s (inside %r)' % (
node, ast.dump(node), condition))
return _convert(main_node)
......@@ -118,6 +118,34 @@ class ExecTest(unittest.TestCase):
'deps': collections.OrderedDict([('a_dep', 'abarb')]),
}, local_scope)
class EvaluateConditionTest(unittest.TestCase):
def test_true(self):
self.assertTrue(gclient_eval.EvaluateCondition('True', {}))
def test_variable(self):
self.assertFalse(gclient_eval.EvaluateCondition('foo', {'foo': 'False'}))
def test_variable_cyclic_reference(self):
with self.assertRaises(ValueError) as cm:
self.assertTrue(gclient_eval.EvaluateCondition('bar', {'bar': 'bar'}))
self.assertIn(
'invalid cyclic reference to \'bar\' (inside \'bar\')',
str(cm.exception))
def test_operators(self):
self.assertFalse(gclient_eval.EvaluateCondition(
'a and not (b or c)', {'a': 'True', 'b': 'False', 'c': 'True'}))
def test_expansion(self):
self.assertTrue(gclient_eval.EvaluateCondition(
'a or b', {'a': 'b and c', 'b': 'not c', 'c': 'False'}))
def test_string_equality(self):
self.assertFalse(gclient_eval.EvaluateCondition(
'foo == "bar"', {'foo': '"baz"'}))
if __name__ == '__main__':
level = logging.DEBUG if '-v' in sys.argv else logging.FATAL
logging.basicConfig(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment