339 lines
10 KiB
Python
339 lines
10 KiB
Python
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
"""Tests for templates module."""
|
|
|
|
import types
|
|
|
|
from absl.testing import parameterized
|
|
import gast
|
|
|
|
from tensorflow.python.autograph.pyct import loader
|
|
from tensorflow.python.autograph.pyct import parser
|
|
from tensorflow.python.autograph.pyct import qual_names as qn
|
|
from tensorflow.python.autograph.pyct import templates
|
|
from tensorflow.python.platform import test
|
|
|
|
|
|
class _CtxClearer(gast.NodeTransformer):
|
|
|
|
def visit(self, node):
|
|
super(_CtxClearer, self).visit(node)
|
|
if hasattr(node, 'ctx'):
|
|
node.ctx = None
|
|
return node
|
|
|
|
|
|
def _parse_with_unset_ctx(expr_source):
|
|
ast_node = parser.parse_expression(expr_source)
|
|
_CtxClearer().visit(ast_node)
|
|
return ast_node
|
|
|
|
|
|
class _CtxChecker(gast.NodeTransformer):
|
|
|
|
def __init__(self, test_instance, expected_ctx):
|
|
self.at_top_level = True
|
|
self.test_instance = test_instance
|
|
self.expected_ctx = expected_ctx
|
|
|
|
def visit(self, node):
|
|
if hasattr(node, 'ctx'):
|
|
self.test_instance.assertIsInstance(node.ctx, self.expected_ctx)
|
|
if self.at_top_level:
|
|
self.at_top_level = False
|
|
self.expected_ctx = gast.Load
|
|
return super(_CtxChecker, self).visit(node)
|
|
|
|
|
|
class TemplatesTest(test.TestCase, parameterized.TestCase):
|
|
|
|
def assertExpectedCtxSet(self, node, ctx):
|
|
"""Assert that node has ctx=ctx at top and ctx=gast.Load everywhere else."""
|
|
checker = _CtxChecker(self, ctx)
|
|
checker.visit(node)
|
|
|
|
def test_replace_tuple(self):
|
|
template = """
|
|
def test_fn(a, c):
|
|
return b,
|
|
"""
|
|
|
|
node = templates.replace(template, b=('a', 'c'))[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
|
|
self.assertEqual((2, 3), result.test_fn(2, 3))
|
|
|
|
def test_replace_variable(self):
|
|
template = """
|
|
def test_fn(a):
|
|
a += 1
|
|
a = 2 * a + 1
|
|
return b
|
|
"""
|
|
|
|
node = templates.replace(template, a='b')[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
self.assertEqual(7, result.test_fn(2))
|
|
|
|
def test_replace_function_name(self):
|
|
template = """
|
|
def fname(a):
|
|
a += 1
|
|
a = 2 * a + 1
|
|
return a
|
|
"""
|
|
|
|
node = templates.replace(template, fname='test_fn')[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
self.assertEqual(7, result.test_fn(2))
|
|
|
|
def test_replace_code_block(self):
|
|
template = """
|
|
def test_fn(a):
|
|
block
|
|
return a
|
|
"""
|
|
|
|
class ShouldBeReplaced(object):
|
|
pass
|
|
|
|
node = templates.replace(
|
|
template,
|
|
block=[
|
|
gast.Assign(
|
|
targets=[
|
|
gast.Name(
|
|
'a',
|
|
ctx=ShouldBeReplaced,
|
|
annotation=None,
|
|
type_comment=None)
|
|
],
|
|
value=gast.BinOp(
|
|
gast.Name(
|
|
'a',
|
|
ctx=ShouldBeReplaced,
|
|
annotation=None,
|
|
type_comment=None), gast.Add(),
|
|
gast.Constant(1, kind=None)),
|
|
),
|
|
] * 2)[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
self.assertEqual(3, result.test_fn(1))
|
|
|
|
def test_replace_attribute(self):
|
|
template = """
|
|
def test_fn(a):
|
|
return a.foo
|
|
"""
|
|
|
|
node = templates.replace(template, foo='b')[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
mod = types.ModuleType('test')
|
|
mod.b = 3
|
|
self.assertEqual(3, result.test_fn(mod))
|
|
|
|
with self.assertRaises(ValueError):
|
|
templates.replace(template, foo=1)
|
|
|
|
def test_replace_attribute_context(self):
|
|
template = """
|
|
def test_fn(foo):
|
|
foo = 0
|
|
"""
|
|
|
|
node = templates.replace(
|
|
template,
|
|
foo=parser.parse_expression('a.b.c'))[0]
|
|
self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store)
|
|
self.assertIsInstance(node.body[0].targets[0].value.ctx, gast.Load)
|
|
self.assertIsInstance(node.body[0].targets[0].value.value.ctx, gast.Load)
|
|
|
|
def test_replace_list_context(self):
|
|
template = """
|
|
def test_fn(foo):
|
|
foo = 0
|
|
"""
|
|
|
|
node = templates.replace(template, foo=parser.parse_expression('[a, b]'))[0]
|
|
self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store)
|
|
self.assertIsInstance(node.body[0].targets[0].elts[0].ctx, gast.Store)
|
|
self.assertIsInstance(node.body[0].targets[0].elts[1].ctx, gast.Store)
|
|
|
|
def test_replace_tuple_context(self):
|
|
template = """
|
|
def test_fn(foo):
|
|
foo = 0
|
|
"""
|
|
|
|
node = templates.replace(template, foo=parser.parse_expression('(a, b)'))[0]
|
|
self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store)
|
|
self.assertIsInstance(node.body[0].targets[0].elts[0].ctx, gast.Store)
|
|
self.assertIsInstance(node.body[0].targets[0].elts[1].ctx, gast.Store)
|
|
|
|
def test_replace_expression_context(self):
|
|
template = """
|
|
def test_fn():
|
|
foo
|
|
"""
|
|
|
|
node = templates.replace(
|
|
template, foo=parser.parse_expression('a + 2 * b / -c'))[0]
|
|
self.assertIsInstance(node.body[0].left.ctx, gast.Load)
|
|
self.assertIsInstance(node.body[0].right.left.right.ctx, gast.Load)
|
|
|
|
def test_replace_complex_context(self):
|
|
template = """
|
|
def test_fn():
|
|
foo = 0
|
|
"""
|
|
|
|
node = templates.replace(
|
|
template, foo=parser.parse_expression('bar(([a, b],)).baz'))[0]
|
|
self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store)
|
|
function_call_arg = node.body[0].targets[0].value.args[0]
|
|
self.assertIsInstance(function_call_arg.elts[0].ctx, gast.Load)
|
|
self.assertIsInstance(function_call_arg.elts[0].elts[0].ctx, gast.Load)
|
|
self.assertIsInstance(function_call_arg.elts[0].elts[1].ctx, gast.Load)
|
|
|
|
def test_replace_index(self):
|
|
template = """
|
|
def test_fn():
|
|
foo = 0
|
|
"""
|
|
|
|
node = templates.replace(
|
|
template, foo=parser.parse_expression('foo(a[b]).bar'))[0]
|
|
function_call_arg = node.body[0].targets[0].value.args[0]
|
|
self.assertIsInstance(function_call_arg.ctx, gast.Load)
|
|
self.assertIsInstance(function_call_arg.slice.ctx, gast.Load)
|
|
|
|
def test_replace_call_keyword(self):
|
|
template = """
|
|
def test_fn():
|
|
def f(a, d, f):
|
|
return a + d + f
|
|
return f(1, kws=None)
|
|
"""
|
|
|
|
source = parser.parse_expression('f(d=3, f=5)')
|
|
node = templates.replace(template, kws=source.keywords)[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
self.assertEqual(9, result.test_fn())
|
|
|
|
with self.assertRaises(ValueError):
|
|
templates.replace(template, kws=[])
|
|
templates.replace(template, kws=1)
|
|
|
|
def test_replace_name_with_call(self):
|
|
template = """
|
|
def test_fn():
|
|
b = 5
|
|
def g(a):
|
|
return 3 * a
|
|
def f():
|
|
return g
|
|
return foo
|
|
"""
|
|
|
|
source = parser.parse_expression('f()(b)')
|
|
node = templates.replace(template, foo=source)[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
self.assertEqual(15, result.test_fn())
|
|
|
|
def test_replace_name_with_dict(self):
|
|
template = """
|
|
def test_fn():
|
|
return foo['bar']
|
|
"""
|
|
|
|
source = parser.parse_expression('{\'bar\': 3}')
|
|
node = templates.replace(template, foo=source)[0]
|
|
result, _, _ = loader.load_ast(node)
|
|
self.assertEqual(3, result.test_fn())
|
|
|
|
def test_replace_as_expression(self):
|
|
template = """
|
|
foo(a)
|
|
"""
|
|
|
|
node = templates.replace_as_expression(template, foo='bar', a='baz')
|
|
self.assertIsInstance(node, gast.Call)
|
|
self.assertEqual(node.func.id, 'bar')
|
|
self.assertEqual(node.args[0].id, 'baz')
|
|
|
|
def test_replace_as_expression_restrictions(self):
|
|
template = """
|
|
foo(a)
|
|
bar(b)
|
|
"""
|
|
with self.assertRaises(ValueError):
|
|
templates.replace_as_expression(template)
|
|
|
|
def test_function_call_in_list(self):
|
|
template = """
|
|
foo(bar)
|
|
"""
|
|
source = parser.parse_expression('[a(b(1))]')
|
|
templates.replace_as_expression(template, bar=source)
|
|
|
|
def test_star_comprehension_in_function_call(self):
|
|
template = """
|
|
a = foo(func, args)
|
|
"""
|
|
source = parser.parse_expression('bar(*[i for i in range(j)])')
|
|
node = templates.replace(template, func=source.func, args=source.args)
|
|
arg_node = node[0].value.args[1].value
|
|
self.assertIsInstance(arg_node.generators[0].target.ctx, gast.Store)
|
|
self.assertIsInstance(arg_node.elt.ctx, gast.Load)
|
|
|
|
def test_lambda_in_function_call(self):
|
|
template = """
|
|
a = foo(arg)
|
|
"""
|
|
source = parser.parse_expression('[lambda i: i]')
|
|
node = templates.replace(template, arg=source)
|
|
lambda_arg = node[0].value.args[0].elts[0]
|
|
self.assertIsInstance(lambda_arg.args.args[0].ctx, gast.Param)
|
|
self.assertIsInstance(lambda_arg.body.ctx, gast.Load)
|
|
|
|
def test_replace_name_with_subscript(self):
|
|
template = """
|
|
foo = bar
|
|
"""
|
|
replacement = qn.QN(qn.QN('dictionary'), subscript=qn.QN('key'))
|
|
|
|
node = templates.replace(template, foo=replacement)[0].targets[0]
|
|
self.assertIsInstance(node.ctx, gast.Store)
|
|
self.assertIsInstance(node.value.ctx, gast.Load)
|
|
|
|
@parameterized.named_parameters([
|
|
('mixed_attr_subscript', 'a.b["c"]'),
|
|
('mixed_subscript_attr', 'a[b.c]'),
|
|
('nested_subscript', 'a[b[c]]'),
|
|
('repeated_subscript', 'a[b][c]'),
|
|
])
|
|
def test_replace_name_mixed_attr_subscript(self, expression_source):
|
|
template = 'foo = bar'
|
|
replacement = _parse_with_unset_ctx(expression_source)
|
|
|
|
target_node = templates.replace(template, foo=replacement)[0].targets[0]
|
|
self.assertExpectedCtxSet(target_node, gast.Store)
|
|
|
|
value_node = templates.replace(template, bar=replacement)[0].value
|
|
self.assertExpectedCtxSet(value_node, gast.Load)
|
|
|
|
if __name__ == '__main__':
|
|
test.main()
|